mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-01 16:02:49 +02:00
Update to v094r09 release.
byuu says: This will easily be the biggest diff in the history of higan. And not in a good way. * target-higan and target-loki have been blown away completely * nall and ruby massively updated * phoenix replaced with hiro (pretty near a total rewrite) * target-higan restarted using hiro (just a window for now) * all emulation cores updated to compile again * installation changed to not require root privileges (installs locally) For the foreseeable future (maybe even permanently?), the new higan UI will only build under Linux/BSD with GTK+ 2.20+. Probably the most likely route for Windows/OS X will be to try and figure out how to build hiro/GTK on those platforms, as awful as that would be. The other alternative would be to produce new UIs for those platforms ... which would actually be a good opportunity to make something much more user friendly. Being that I just started on this a few hours ago, that means that for at least a few weeks, don't expect to be able to actually play any games. Right now, you can pretty much just compile the binary and that's it. It's quite possible that some nall changes didn't produce compilation errors, but will produce runtime errors. So until the UI can actually load games, we won't know if anything is broken. But we should mostly be okay. It was mostly just trim<1> -> trim changes, moving to Hash::SHA256 (much cleaner), and patching some reckless memory copy functions enough to compile. Progress isn't going to be like it was before: I'm now dividing my time much thinner between studying and other hobbies. My aim this time is not to produce a binary for everyone to play games on. Rather, it's to keep the emulator alive. I want to be able to apply critical patches again. And I would also like the base of the emulator to live on, for use in other emulator frontends that utilize higan.
This commit is contained in:
31
hiro/gtk/action/action.cpp
Normal file
31
hiro/gtk/action/action.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pAction::construct() -> void {
|
||||
}
|
||||
|
||||
auto pAction::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pAction::setEnabled(bool enabled) -> void {
|
||||
gtk_widget_set_sensitive(widget, enabled);
|
||||
}
|
||||
|
||||
auto pAction::setFont(const string& font) -> void {
|
||||
pFont::setFont(widget, font);
|
||||
}
|
||||
|
||||
auto pAction::setVisible(bool visible) -> void {
|
||||
gtk_widget_set_visible(widget, visible);
|
||||
}
|
||||
|
||||
//GTK+ uses _ for mnemonics, __ for _
|
||||
//transform so that & is used for mnemonics, && for &
|
||||
auto pAction::_mnemonic(string text) -> string {
|
||||
text.transform("&_", "\x01\x02");
|
||||
text.replace("\x01\x01", "&");
|
||||
text.transform("\x01", "_");
|
||||
text.replace("\x02", "__");
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
15
hiro/gtk/action/action.hpp
Normal file
15
hiro/gtk/action/action.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pAction : pObject {
|
||||
Declare(Action, Object)
|
||||
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto _mnemonic(string text) -> string;
|
||||
|
||||
GtkWidget* widget = nullptr;
|
||||
};
|
||||
|
||||
}
|
34
hiro/gtk/action/menu-check-item.cpp
Normal file
34
hiro/gtk/action/menu-check-item.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto MenuCheckItem_toggle(GtkCheckMenuItem* gtkCheckMenuItem, pMenuCheckItem* p) -> void {
|
||||
p->state().checked = gtk_check_menu_item_get_active(gtkCheckMenuItem);
|
||||
if(!p->locked()) p->self().doToggle();
|
||||
}
|
||||
|
||||
auto pMenuCheckItem::construct() -> void {
|
||||
widget = gtk_check_menu_item_new_with_mnemonic("");
|
||||
setChecked(state().checked);
|
||||
setText(state().text);
|
||||
g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(MenuCheckItem_toggle), (gpointer)this);
|
||||
}
|
||||
|
||||
auto pMenuCheckItem::destruct() -> void {
|
||||
gtk_widget_destroy(widget);
|
||||
}
|
||||
|
||||
auto pMenuCheckItem::orphan() -> void {
|
||||
destruct();
|
||||
construct();
|
||||
}
|
||||
|
||||
auto pMenuCheckItem::setChecked(bool checked) -> void {
|
||||
lock();
|
||||
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), checked);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pMenuCheckItem::setText(const string& text) -> void {
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _mnemonic(text));
|
||||
}
|
||||
|
||||
}
|
11
hiro/gtk/action/menu-check-item.hpp
Normal file
11
hiro/gtk/action/menu-check-item.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenuCheckItem : pAction {
|
||||
Declare(MenuCheckItem, Action)
|
||||
auto orphan() -> void;
|
||||
|
||||
auto setChecked(bool checked) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
};
|
||||
|
||||
}
|
30
hiro/gtk/action/menu-item.cpp
Normal file
30
hiro/gtk/action/menu-item.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto MenuItem_activate(GtkMenuItem*, pMenuItem* p) -> void {
|
||||
p->self().doActivate();
|
||||
}
|
||||
|
||||
auto pMenuItem::construct() -> void {
|
||||
widget = gtk_image_menu_item_new_with_mnemonic("");
|
||||
g_signal_connect(G_OBJECT(widget), "activate", G_CALLBACK(MenuItem_activate), (gpointer)this);
|
||||
setText(state().text);
|
||||
}
|
||||
|
||||
auto pMenuItem::destruct() -> void {
|
||||
if(widget) gtk_widget_destroy(widget), widget = nullptr;
|
||||
}
|
||||
|
||||
auto pMenuItem::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
GtkImage* gtkImage = CreateImage(icon, true);
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage);
|
||||
} else {
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuItem::setText(const string& text) -> void {
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _mnemonic(text));
|
||||
}
|
||||
|
||||
}
|
10
hiro/gtk/action/menu-item.hpp
Normal file
10
hiro/gtk/action/menu-item.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenuItem : pAction {
|
||||
Declare(MenuItem, Action)
|
||||
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
};
|
||||
|
||||
}
|
75
hiro/gtk/action/menu-radio-item.cpp
Normal file
75
hiro/gtk/action/menu-radio-item.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto MenuRadioItem_activate(GtkCheckMenuItem* gtkCheckMenuItem, pMenuRadioItem* p) -> void {
|
||||
p->_doActivate();
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::construct() -> void {
|
||||
widget = gtk_radio_menu_item_new_with_mnemonic(0, "");
|
||||
setGroup(state().group);
|
||||
setText(state().text);
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self()) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->self()->widget), item->checked());
|
||||
}
|
||||
}
|
||||
g_signal_connect(G_OBJECT(widget), "toggled", G_CALLBACK(MenuRadioItem_activate), (gpointer)this);
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::destruct() -> void {
|
||||
if(widget) gtk_widget_destroy(widget), widget = nullptr;
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::setChecked() -> void {
|
||||
_parent().lock();
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self()) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->self()->widget), false);
|
||||
}
|
||||
}
|
||||
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), true);
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::setGroup(const vector<shared_pointer_weak<mMenuRadioItem>>& group) -> void {
|
||||
_parent().lock();
|
||||
shared_pointer<mMenuRadioItem> first;
|
||||
for(auto& weak : group) {
|
||||
if(!first) {
|
||||
first = weak.acquire();
|
||||
continue;
|
||||
}
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self() && first->self()) {
|
||||
GSList* currentGroup = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(first->self()->widget));
|
||||
if(currentGroup != gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item->self()->widget))) {
|
||||
gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(item->self()->widget), currentGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::setText(const string& text) -> void {
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _mnemonic(text));
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::_doActivate() -> void {
|
||||
if(!_parent().locked()) {
|
||||
bool wasChecked = state().checked;
|
||||
self().setChecked();
|
||||
if(!wasChecked) self().doActivate();
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuRadioItem::_parent() -> pMenuRadioItem& {
|
||||
if(state().group.size()) {
|
||||
if(auto item = state().group.first().acquire()) {
|
||||
if(item->self()) return *item->self();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/action/menu-radio-item.hpp
Normal file
14
hiro/gtk/action/menu-radio-item.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenuRadioItem : pAction {
|
||||
Declare(MenuRadioItem, Action)
|
||||
|
||||
auto setChecked() -> void;
|
||||
auto setGroup(const vector<shared_pointer_weak<mMenuRadioItem>>& group) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _doActivate() -> void;
|
||||
auto _parent() -> pMenuRadioItem&;
|
||||
};
|
||||
|
||||
}
|
11
hiro/gtk/action/menu-separator.cpp
Normal file
11
hiro/gtk/action/menu-separator.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pMenuSeparator::construct() -> void {
|
||||
widget = gtk_separator_menu_item_new();
|
||||
}
|
||||
|
||||
auto pMenuSeparator::destruct() -> void {
|
||||
if(widget) gtk_widget_destroy(widget), widget = nullptr;
|
||||
}
|
||||
|
||||
}
|
7
hiro/gtk/action/menu-separator.hpp
Normal file
7
hiro/gtk/action/menu-separator.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenuSeparator : pAction {
|
||||
Declare(MenuSeparator, Action)
|
||||
};
|
||||
|
||||
}
|
48
hiro/gtk/action/menu.cpp
Normal file
48
hiro/gtk/action/menu.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pMenu::construct() -> void {
|
||||
gtkMenu = gtk_menu_new();
|
||||
widget = gtk_image_menu_item_new_with_mnemonic("");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), gtkMenu);
|
||||
setText(state().text);
|
||||
|
||||
for(auto& action : state().actions) append(*action);
|
||||
}
|
||||
|
||||
auto pMenu::destruct() -> void {
|
||||
gtk_widget_destroy(gtkMenu);
|
||||
gtk_widget_destroy(widget);
|
||||
}
|
||||
|
||||
auto pMenu::append(sAction action) -> void {
|
||||
if(action->self()) {
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), action->self()->widget);
|
||||
action->self()->setFont(action->font(true));
|
||||
action->self()->setVisible(action->visible(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenu::remove(sAction action) -> void {
|
||||
}
|
||||
|
||||
auto pMenu::setFont(const string& font) -> void {
|
||||
pAction::setFont(font);
|
||||
for(auto& action : state().actions) {
|
||||
if(action->self()) action->self()->setFont(action->font(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenu::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
GtkImage* gtkImage = CreateImage(icon, true);
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage);
|
||||
} else {
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenu::setText(const string& text) -> void {
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), _mnemonic(text));
|
||||
}
|
||||
|
||||
}
|
15
hiro/gtk/action/menu.hpp
Normal file
15
hiro/gtk/action/menu.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenu : pAction {
|
||||
Declare(Menu, Action)
|
||||
|
||||
auto append(sAction action) -> void;
|
||||
auto remove(sAction action) -> void;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
GtkWidget* gtkMenu = nullptr;
|
||||
};
|
||||
|
||||
}
|
84
hiro/gtk/application.cpp
Normal file
84
hiro/gtk/application.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace hiro {
|
||||
|
||||
XlibDisplay* pApplication::display = nullptr;
|
||||
|
||||
void pApplication::run() {
|
||||
if(Application::state.onMain) {
|
||||
while(Application::state.quit == false) {
|
||||
processEvents();
|
||||
Application::doMain();
|
||||
}
|
||||
} else {
|
||||
gtk_main();
|
||||
}
|
||||
}
|
||||
|
||||
bool pApplication::pendingEvents() {
|
||||
return gtk_events_pending();
|
||||
}
|
||||
|
||||
void pApplication::processEvents() {
|
||||
while(pendingEvents()) gtk_main_iteration_do(false);
|
||||
}
|
||||
|
||||
void pApplication::quit() {
|
||||
//if gtk_main() was invoked, call gtk_main_quit()
|
||||
if(gtk_main_level()) gtk_main_quit();
|
||||
}
|
||||
|
||||
void pApplication::initialize() {
|
||||
display = XOpenDisplay(nullptr);
|
||||
|
||||
settings = new Settings;
|
||||
settings->load();
|
||||
|
||||
//set WM_CLASS to Application::name()
|
||||
if(Application::state.name) gdk_set_program_class(Application::state.name);
|
||||
|
||||
#if 1
|
||||
int argc = 1;
|
||||
char* argv[] = {new char[8], nullptr};
|
||||
strcpy(argv[0], "phoenix");
|
||||
#else
|
||||
//--g-fatal-warnings will force a trap on Gtk-CRITICAL errors
|
||||
//this allows gdb to perform a backtrace to find error origin point
|
||||
int argc = 2;
|
||||
char* argv[] = {new char[8], new char[19], nullptr};
|
||||
strcpy(argv[0], "phoenix");
|
||||
strcpy(argv[1], "--g-fatal-warnings");
|
||||
#endif
|
||||
char** argvp = argv;
|
||||
gtk_init(&argc, &argvp);
|
||||
|
||||
GtkSettings* gtkSettings = gtk_settings_get_default();
|
||||
g_object_set(gtkSettings, "gtk-button-images", true, nullptr);
|
||||
|
||||
gtk_rc_parse_string(R"(
|
||||
style "PhoenixWindow"
|
||||
{
|
||||
GtkWindow::resize-grip-width = 0
|
||||
GtkWindow::resize-grip-height = 0
|
||||
}
|
||||
class "GtkWindow" style "PhoenixWindow"
|
||||
|
||||
style "PhoenixTreeView"
|
||||
{
|
||||
GtkTreeView::vertical-separator = 0
|
||||
}
|
||||
class "GtkTreeView" style "PhoenixTreeView"
|
||||
|
||||
style "PhoenixTabFrameCloseButton"
|
||||
{
|
||||
GtkWidget::focus-line-width = 0
|
||||
GtkWidget::focus-padding = 0
|
||||
GtkButton::default-border = {0, 0, 0, 0}
|
||||
GtkButton::default-outer-border = {0, 0, 0, 0}
|
||||
GtkButton::inner-border = {0, 1, 0, 0}
|
||||
}
|
||||
widget_class "*.<GtkNotebook>.<GtkHBox>.<GtkButton>" style "PhoenixTabFrameCloseButton"
|
||||
)");
|
||||
|
||||
pKeyboard::initialize();
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/application.hpp
Normal file
14
hiro/gtk/application.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pApplication {
|
||||
static XlibDisplay* display;
|
||||
|
||||
static void run();
|
||||
static bool pendingEvents();
|
||||
static void processEvents();
|
||||
static void quit();
|
||||
|
||||
static void initialize();
|
||||
};
|
||||
|
||||
}
|
88
hiro/gtk/browser-window.cpp
Normal file
88
hiro/gtk/browser-window.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
namespace hiro {
|
||||
|
||||
static void BrowserWindow_addFilters(GtkWidget* dialog, lstring filters) {
|
||||
for(auto& filter : filters) {
|
||||
GtkFileFilter* gtkFilter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(gtkFilter, filter);
|
||||
lstring patterns = filter.split<1>("(")(1).rtrim(")").split(",").strip();
|
||||
for(auto& pattern : patterns) gtk_file_filter_add_pattern(gtkFilter, pattern);
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
auto pBrowserWindow::directory(BrowserWindow::State& state) -> string {
|
||||
string name;
|
||||
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(
|
||||
state.title ? state.title : "Select Directory",
|
||||
state.parent && state.parent->self() ? GTK_WINDOW(state.parent->self()->widget) : (GtkWindow*)nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
||||
(const gchar*)nullptr
|
||||
);
|
||||
|
||||
if(state.path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), state.path);
|
||||
|
||||
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
char* temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
name = temp;
|
||||
g_free(temp);
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
if(name && !name.endsWith("/")) name.append("/");
|
||||
return name;
|
||||
}
|
||||
|
||||
auto pBrowserWindow::open(BrowserWindow::State& state) -> string {
|
||||
string name;
|
||||
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(
|
||||
state.title ? state.title : "Open File",
|
||||
state.parent && state.parent->self() ? GTK_WINDOW(state.parent->self()->widget) : (GtkWindow*)nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
||||
(const gchar*)nullptr
|
||||
);
|
||||
|
||||
if(state.path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), state.path);
|
||||
BrowserWindow_addFilters(dialog, state.filters);
|
||||
|
||||
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
char* temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
name = temp;
|
||||
g_free(temp);
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
return name;
|
||||
}
|
||||
|
||||
auto pBrowserWindow::save(BrowserWindow::State& state) -> string {
|
||||
string name;
|
||||
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(
|
||||
state.title ? state.title : "Save File",
|
||||
state.parent && state.parent->self() ? GTK_WINDOW(state.parent->self()->widget) : (GtkWindow*)nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
||||
(const gchar*)nullptr
|
||||
);
|
||||
|
||||
if(state.path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), state.path);
|
||||
BrowserWindow_addFilters(dialog, state.filters);
|
||||
|
||||
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
char* temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
name = temp;
|
||||
g_free(temp);
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
9
hiro/gtk/browser-window.hpp
Normal file
9
hiro/gtk/browser-window.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pBrowserWindow {
|
||||
static auto directory(BrowserWindow::State& state) -> string;
|
||||
static auto open(BrowserWindow::State& state) -> string;
|
||||
static auto save(BrowserWindow::State& state) -> string;
|
||||
};
|
||||
|
||||
}
|
40
hiro/gtk/desktop.cpp
Normal file
40
hiro/gtk/desktop.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace hiro {
|
||||
|
||||
Size pDesktop::size() {
|
||||
return {
|
||||
gdk_screen_get_width(gdk_screen_get_default()),
|
||||
gdk_screen_get_height(gdk_screen_get_default())
|
||||
};
|
||||
}
|
||||
|
||||
Geometry pDesktop::workspace() {
|
||||
XlibDisplay* display = XOpenDisplay(nullptr);
|
||||
int screen = DefaultScreen(display);
|
||||
|
||||
static Atom atom = XlibNone;
|
||||
if(atom == XlibNone) atom = XInternAtom(display, "_NET_WORKAREA", True);
|
||||
|
||||
int format;
|
||||
unsigned char* data = nullptr;
|
||||
unsigned long items, after;
|
||||
Atom returnAtom;
|
||||
|
||||
int result = XGetWindowProperty(
|
||||
display, RootWindow(display, screen), atom, 0, 4, False, XA_CARDINAL, &returnAtom, &format, &items, &after, &data
|
||||
);
|
||||
|
||||
XCloseDisplay(display);
|
||||
|
||||
if(result == Success && returnAtom == XA_CARDINAL && format == 32 && items == 4) {
|
||||
unsigned long *workarea = (unsigned long*)data;
|
||||
return {(signed)workarea[0], (signed)workarea[1], (signed)workarea[2], (signed)workarea[3]};
|
||||
}
|
||||
|
||||
return {
|
||||
0, 0,
|
||||
gdk_screen_get_width(gdk_screen_get_default()),
|
||||
gdk_screen_get_height(gdk_screen_get_default())
|
||||
};
|
||||
}
|
||||
|
||||
}
|
8
hiro/gtk/desktop.hpp
Normal file
8
hiro/gtk/desktop.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pDesktop {
|
||||
static Size size();
|
||||
static Geometry workspace();
|
||||
};
|
||||
|
||||
}
|
77
hiro/gtk/font.cpp
Normal file
77
hiro/gtk/font.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace hiro {
|
||||
|
||||
string pFont::serif(unsigned size, string style) {
|
||||
if(size == 0) size = 8;
|
||||
if(style == "") style = "Normal";
|
||||
return {"Serif, ", size, ", ", style};
|
||||
}
|
||||
|
||||
string pFont::sans(unsigned size, string style) {
|
||||
if(size == 0) size = 8;
|
||||
if(style == "") style = "Normal";
|
||||
return {"Sans, ", size, ", ", style};
|
||||
}
|
||||
|
||||
string pFont::monospace(unsigned size, string style) {
|
||||
if(size == 0) size = 8;
|
||||
return {"Liberation Mono, ", size, ", ", style};
|
||||
}
|
||||
|
||||
Size pFont::size(string font, string text) {
|
||||
PangoFontDescription* description = create(font);
|
||||
Size size = pFont::size(description, text);
|
||||
free(description);
|
||||
return size;
|
||||
}
|
||||
|
||||
PangoFontDescription* pFont::create(string description) {
|
||||
lstring part = description.split<2>(",").strip();
|
||||
|
||||
string family = "Sans";
|
||||
unsigned size = 8u;
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
|
||||
if(part[0] != "") family = part[0];
|
||||
if(part.size() >= 2) size = decimal(part[1]);
|
||||
if(part.size() >= 3) bold = (bool)part[2].find("Bold");
|
||||
if(part.size() >= 3) italic = (bool)part[2].find("Italic");
|
||||
|
||||
PangoFontDescription* font = pango_font_description_new();
|
||||
pango_font_description_set_family(font, family);
|
||||
pango_font_description_set_size(font, size * PANGO_SCALE);
|
||||
pango_font_description_set_weight(font, !bold ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD);
|
||||
pango_font_description_set_style(font, !italic ? PANGO_STYLE_NORMAL : PANGO_STYLE_OBLIQUE);
|
||||
return font;
|
||||
}
|
||||
|
||||
void pFont::free(PangoFontDescription* font) {
|
||||
pango_font_description_free(font);
|
||||
}
|
||||
|
||||
Size pFont::size(PangoFontDescription* font, string text) {
|
||||
PangoContext* context = gdk_pango_context_get_for_screen(gdk_screen_get_default());
|
||||
PangoLayout* layout = pango_layout_new(context);
|
||||
pango_layout_set_font_description(layout, font);
|
||||
pango_layout_set_text(layout, text, -1);
|
||||
int width = 0, height = 0;
|
||||
pango_layout_get_pixel_size(layout, &width, &height);
|
||||
g_object_unref((gpointer)layout);
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
void pFont::setFont(GtkWidget* widget, string font) {
|
||||
auto gtkFont = pFont::create(font);
|
||||
pFont::setFont(widget, (gpointer)gtkFont);
|
||||
pFont::free(gtkFont);
|
||||
}
|
||||
|
||||
void pFont::setFont(GtkWidget* widget, gpointer font) {
|
||||
if(font == nullptr) return;
|
||||
gtk_widget_modify_font(widget, (PangoFontDescription*)font);
|
||||
if(GTK_IS_CONTAINER(widget)) {
|
||||
gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)pFont::setFont, font);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
16
hiro/gtk/font.hpp
Normal file
16
hiro/gtk/font.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pFont {
|
||||
static string serif(unsigned size, string style);
|
||||
static string sans(unsigned size, string style);
|
||||
static string monospace(unsigned size, string style);
|
||||
static Size size(string font, string text);
|
||||
|
||||
static PangoFontDescription* create(string description);
|
||||
static void free(PangoFontDescription* font);
|
||||
static Size size(PangoFontDescription* font, string text);
|
||||
static void setFont(GtkWidget* widget, string font);
|
||||
static void setFont(GtkWidget* widget, gpointer font);
|
||||
};
|
||||
|
||||
}
|
11
hiro/gtk/header.hpp
Normal file
11
hiro/gtk/header.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <nall/xorg/guard.hpp>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gdk/gdkkeysyms.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gtksourceview/gtksourceview.h>
|
||||
#include <gtksourceview/gtksourcelanguagemanager.h>
|
||||
#include <gtksourceview/gtksourcestyleschememanager.h>
|
||||
#include <cairo.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <nall/xorg/guard.hpp>
|
9
hiro/gtk/hotkey.cpp
Normal file
9
hiro/gtk/hotkey.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pHotkey::construct() -> void {
|
||||
}
|
||||
|
||||
auto pHotkey::destruct() -> void {
|
||||
}
|
||||
|
||||
}
|
7
hiro/gtk/hotkey.hpp
Normal file
7
hiro/gtk/hotkey.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pHotkey : pObject {
|
||||
Declare(Hotkey, Object)
|
||||
};
|
||||
|
||||
}
|
344
hiro/gtk/keyboard.cpp
Normal file
344
hiro/gtk/keyboard.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pKeyboard::poll() -> vector<bool> {
|
||||
vector<bool> result;
|
||||
char state[256];
|
||||
XQueryKeymap(pApplication::display, state);
|
||||
for(auto& code : settings->keycodes) {
|
||||
result.append(_pressed(state, code));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto pKeyboard::pressed(unsigned code) -> bool {
|
||||
char state[256];
|
||||
XQueryKeymap(pApplication::display, state);
|
||||
return _pressed(state, code);
|
||||
}
|
||||
|
||||
auto pKeyboard::_pressed(char* state, uint16_t code) -> bool {
|
||||
uint8_t lo = code >> 0;
|
||||
uint8_t hi = code >> 8;
|
||||
if(lo && state[lo >> 3] & (1 << (lo & 7))) return true;
|
||||
if(hi && state[hi >> 3] & (1 << (hi & 7))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pKeyboard::_translate(unsigned code) -> signed {
|
||||
switch(code) {
|
||||
case GDK_Escape: return 0;
|
||||
case GDK_F1: return 0;
|
||||
case GDK_F2: return 0;
|
||||
case GDK_F3: return 0;
|
||||
case GDK_F4: return 0;
|
||||
case GDK_F5: return 0;
|
||||
case GDK_F6: return 0;
|
||||
case GDK_F7: return 0;
|
||||
case GDK_F8: return 0;
|
||||
case GDK_F9: return 0;
|
||||
case GDK_F10: return 0;
|
||||
case GDK_F11: return 0;
|
||||
case GDK_F12: return 0;
|
||||
|
||||
case GDK_Print: return 0;
|
||||
case GDK_Scroll_Lock: return 0;
|
||||
case GDK_Pause: return 0;
|
||||
|
||||
case GDK_Insert: return 0;
|
||||
case GDK_Delete: return 0;
|
||||
case GDK_Home: return 0;
|
||||
case GDK_End: return 0;
|
||||
case GDK_Prior: return 0;
|
||||
case GDK_Next: return 0;
|
||||
|
||||
case GDK_Up: return 0;
|
||||
case GDK_Down: return 0;
|
||||
case GDK_Left: return 0;
|
||||
case GDK_Right: return 0;
|
||||
|
||||
case GDK_grave: return '`';
|
||||
case GDK_1: return '1';
|
||||
case GDK_2: return '2';
|
||||
case GDK_3: return '3';
|
||||
case GDK_4: return '4';
|
||||
case GDK_5: return '5';
|
||||
case GDK_6: return '6';
|
||||
case GDK_7: return '7';
|
||||
case GDK_8: return '8';
|
||||
case GDK_9: return '9';
|
||||
case GDK_0: return '0';
|
||||
case GDK_minus: return '-';
|
||||
case GDK_equal: return '=';
|
||||
case GDK_BackSpace: return '\b';
|
||||
|
||||
case GDK_asciitilde: return '~';
|
||||
case GDK_exclam: return '!';
|
||||
case GDK_at: return '@';
|
||||
case GDK_numbersign: return '#';
|
||||
case GDK_dollar: return '$';
|
||||
case GDK_percent: return '%';
|
||||
case GDK_asciicircum: return '^';
|
||||
case GDK_ampersand: return '&';
|
||||
case GDK_asterisk: return '*';
|
||||
case GDK_parenleft: return '(';
|
||||
case GDK_parenright: return ')';
|
||||
case GDK_underscore: return '_';
|
||||
case GDK_plus: return '+';
|
||||
|
||||
case GDK_Tab: return '\t';
|
||||
case GDK_Caps_Lock: return 0;
|
||||
case GDK_Return: return '\n';
|
||||
case GDK_Shift_L: return 0;
|
||||
case GDK_Shift_R: return 0;
|
||||
case GDK_Control_L: return 0;
|
||||
case GDK_Control_R: return 0;
|
||||
case GDK_Alt_L: return 0;
|
||||
case GDK_Alt_R: return 0;
|
||||
case GDK_Super_L: return 0;
|
||||
case GDK_Super_R: return 0;
|
||||
case GDK_Menu: return 0;
|
||||
case GDK_space: return ' ';
|
||||
|
||||
case GDK_bracketleft: return '[';
|
||||
case GDK_bracketright: return ']';
|
||||
case GDK_backslash: return '\\';
|
||||
case GDK_semicolon: return ';';
|
||||
case GDK_apostrophe: return '\'';
|
||||
case GDK_comma: return ',';
|
||||
case GDK_period: return '.';
|
||||
case GDK_slash: return '/';
|
||||
|
||||
case GDK_braceleft: return '{';
|
||||
case GDK_braceright: return '}';
|
||||
case GDK_bar: return '|';
|
||||
case GDK_colon: return ':';
|
||||
case GDK_quotedbl: return '\"';
|
||||
case GDK_less: return '<';
|
||||
case GDK_greater: return '>';
|
||||
case GDK_question: return '?';
|
||||
|
||||
case GDK_A: return 'A';
|
||||
case GDK_B: return 'B';
|
||||
case GDK_C: return 'C';
|
||||
case GDK_D: return 'D';
|
||||
case GDK_E: return 'E';
|
||||
case GDK_F: return 'F';
|
||||
case GDK_G: return 'G';
|
||||
case GDK_H: return 'H';
|
||||
case GDK_I: return 'I';
|
||||
case GDK_J: return 'J';
|
||||
case GDK_K: return 'K';
|
||||
case GDK_L: return 'L';
|
||||
case GDK_M: return 'M';
|
||||
case GDK_N: return 'N';
|
||||
case GDK_O: return 'O';
|
||||
case GDK_P: return 'P';
|
||||
case GDK_Q: return 'Q';
|
||||
case GDK_R: return 'R';
|
||||
case GDK_S: return 'S';
|
||||
case GDK_T: return 'T';
|
||||
case GDK_U: return 'U';
|
||||
case GDK_V: return 'V';
|
||||
case GDK_W: return 'W';
|
||||
case GDK_X: return 'X';
|
||||
case GDK_Y: return 'Y';
|
||||
case GDK_Z: return 'Z';
|
||||
|
||||
case GDK_a: return 'a';
|
||||
case GDK_b: return 'b';
|
||||
case GDK_c: return 'c';
|
||||
case GDK_d: return 'd';
|
||||
case GDK_e: return 'e';
|
||||
case GDK_f: return 'f';
|
||||
case GDK_g: return 'g';
|
||||
case GDK_h: return 'h';
|
||||
case GDK_i: return 'i';
|
||||
case GDK_j: return 'j';
|
||||
case GDK_k: return 'k';
|
||||
case GDK_l: return 'l';
|
||||
case GDK_m: return 'm';
|
||||
case GDK_n: return 'n';
|
||||
case GDK_o: return 'o';
|
||||
case GDK_p: return 'p';
|
||||
case GDK_q: return 'q';
|
||||
case GDK_r: return 'r';
|
||||
case GDK_s: return 's';
|
||||
case GDK_t: return 't';
|
||||
case GDK_u: return 'u';
|
||||
case GDK_v: return 'v';
|
||||
case GDK_w: return 'w';
|
||||
case GDK_x: return 'x';
|
||||
case GDK_y: return 'y';
|
||||
case GDK_z: return 'z';
|
||||
|
||||
case GDK_Num_Lock: return 0;
|
||||
case GDK_KP_Divide: return '/';
|
||||
case GDK_KP_Multiply: return '*';
|
||||
case GDK_KP_Subtract: return '-';
|
||||
case GDK_KP_Add: return '+';
|
||||
case GDK_KP_Enter: return '\n';
|
||||
case GDK_KP_Decimal: return '.';
|
||||
|
||||
case GDK_KP_1: return '1';
|
||||
case GDK_KP_2: return '2';
|
||||
case GDK_KP_3: return '3';
|
||||
case GDK_KP_4: return '4';
|
||||
case GDK_KP_5: return '5';
|
||||
case GDK_KP_6: return '6';
|
||||
case GDK_KP_7: return '7';
|
||||
case GDK_KP_8: return '8';
|
||||
case GDK_KP_9: return '9';
|
||||
case GDK_KP_0: return '0';
|
||||
|
||||
case GDK_KP_Home: return 0;
|
||||
case GDK_KP_End: return 0;
|
||||
case GDK_KP_Page_Up: return 0;
|
||||
case GDK_KP_Page_Down: return 0;
|
||||
case GDK_KP_Up: return 0;
|
||||
case GDK_KP_Down: return 0;
|
||||
case GDK_KP_Left: return 0;
|
||||
case GDK_KP_Right: return 0;
|
||||
case GDK_KP_Begin: return 0;
|
||||
case GDK_KP_Insert: return 0;
|
||||
case GDK_KP_Delete: return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto pKeyboard::initialize() -> void {
|
||||
auto append = [](unsigned lo, unsigned hi = 0) {
|
||||
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0;
|
||||
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0;
|
||||
settings->keycodes.append(lo | (hi << 8));
|
||||
};
|
||||
|
||||
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
|
||||
for(auto& key : Keyboard::keys) {
|
||||
map("Escape", XK_Escape);
|
||||
map("F1", XK_F1);
|
||||
map("F2", XK_F2);
|
||||
map("F3", XK_F3);
|
||||
map("F4", XK_F4);
|
||||
map("F5", XK_F5);
|
||||
map("F6", XK_F6);
|
||||
map("F7", XK_F7);
|
||||
map("F8", XK_F8);
|
||||
map("F9", XK_F9);
|
||||
map("F10", XK_F10);
|
||||
map("F11", XK_F11);
|
||||
map("F12", XK_F12);
|
||||
|
||||
map("PrintScreen", XK_Print);
|
||||
map("ScrollLock", XK_Scroll_Lock);
|
||||
map("Pause", XK_Pause);
|
||||
|
||||
map("Insert", XK_Insert);
|
||||
map("Delete", XK_Delete);
|
||||
map("Home", XK_Home);
|
||||
map("End", XK_End);
|
||||
map("PageUp", XK_Prior);
|
||||
map("PageDown", XK_Next);
|
||||
|
||||
map("Up", XK_Up);
|
||||
map("Down", XK_Down);
|
||||
map("Left", XK_Left);
|
||||
map("Right", XK_Right);
|
||||
|
||||
map("Grave", XK_asciitilde);
|
||||
map("1", XK_1);
|
||||
map("2", XK_2);
|
||||
map("3", XK_3);
|
||||
map("4", XK_4);
|
||||
map("5", XK_5);
|
||||
map("6", XK_6);
|
||||
map("7", XK_7);
|
||||
map("8", XK_8);
|
||||
map("9", XK_9);
|
||||
map("0", XK_0);
|
||||
map("Dash", XK_minus);
|
||||
map("Equal", XK_equal);
|
||||
map("Backspace", XK_BackSpace);
|
||||
|
||||
map("Tab", XK_Tab);
|
||||
map("CapsLock", XK_Caps_Lock);
|
||||
map("LeftEnter", XK_Return);
|
||||
map("LeftShift", XK_Shift_L);
|
||||
map("RightShift", XK_Shift_R);
|
||||
map("LeftControl", XK_Control_L);
|
||||
map("RightControl", XK_Control_R);
|
||||
map("LeftAlt", XK_Alt_L);
|
||||
map("RightAlt", XK_Alt_R);
|
||||
map("LeftSuper", XK_Super_L);
|
||||
map("RightSuper", XK_Super_R);
|
||||
map("Menu", XK_Menu);
|
||||
map("Space", XK_space);
|
||||
|
||||
map("OpenBracket", XK_bracketleft);
|
||||
map("CloseBracket", XK_bracketright);
|
||||
map("Backslash", XK_backslash);
|
||||
map("Semicolon", XK_semicolon);
|
||||
map("Apostrophe", XK_apostrophe);
|
||||
map("Comma", XK_comma);
|
||||
map("Period", XK_period);
|
||||
map("Slash", XK_slash);
|
||||
|
||||
map("A", XK_A);
|
||||
map("B", XK_B);
|
||||
map("C", XK_C);
|
||||
map("D", XK_D);
|
||||
map("E", XK_E);
|
||||
map("F", XK_F);
|
||||
map("G", XK_G);
|
||||
map("H", XK_H);
|
||||
map("I", XK_I);
|
||||
map("J", XK_J);
|
||||
map("K", XK_K);
|
||||
map("L", XK_L);
|
||||
map("M", XK_M);
|
||||
map("N", XK_N);
|
||||
map("O", XK_O);
|
||||
map("P", XK_P);
|
||||
map("Q", XK_Q);
|
||||
map("R", XK_R);
|
||||
map("S", XK_S);
|
||||
map("T", XK_T);
|
||||
map("U", XK_U);
|
||||
map("V", XK_V);
|
||||
map("W", XK_W);
|
||||
map("X", XK_X);
|
||||
map("Y", XK_Y);
|
||||
map("Z", XK_Z);
|
||||
|
||||
map("NumLock", XK_Num_Lock);
|
||||
map("Divide", XK_KP_Divide);
|
||||
map("Multiply", XK_KP_Multiply);
|
||||
map("Subtract", XK_KP_Subtract);
|
||||
map("Add", XK_KP_Add);
|
||||
map("RightEnter", XK_KP_Enter);
|
||||
map("Point", XK_KP_Decimal);
|
||||
|
||||
map("One", XK_KP_1);
|
||||
map("Two", XK_KP_2);
|
||||
map("Three", XK_KP_3);
|
||||
map("Four", XK_KP_4);
|
||||
map("Five", XK_KP_5);
|
||||
map("Six", XK_KP_6);
|
||||
map("Seven", XK_KP_7);
|
||||
map("Eight", XK_KP_8);
|
||||
map("Nine", XK_KP_9);
|
||||
map("Zero", XK_KP_0);
|
||||
|
||||
map("Shift", XK_Shift_L, XK_Shift_R);
|
||||
map("Control", XK_Control_L, XK_Control_R);
|
||||
map("Alt", XK_Alt_L, XK_Alt_R);
|
||||
map("Super", XK_Super_L, XK_Super_R);
|
||||
map("Enter", XK_Return, XK_KP_Enter);
|
||||
|
||||
print("[phoenix/gtk] error: unhandled key: ", key, "\n");
|
||||
append(0);
|
||||
}
|
||||
#undef map
|
||||
}
|
||||
|
||||
}
|
13
hiro/gtk/keyboard.hpp
Normal file
13
hiro/gtk/keyboard.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pKeyboard {
|
||||
static auto poll() -> vector<bool>;
|
||||
static auto pressed(unsigned code) -> bool;
|
||||
|
||||
static auto _pressed(char* state, uint16_t code) -> bool;
|
||||
static auto _translate(unsigned code) -> signed;
|
||||
|
||||
static auto initialize() -> void;
|
||||
};
|
||||
|
||||
}
|
29
hiro/gtk/layout.cpp
Normal file
29
hiro/gtk/layout.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pLayout::construct() -> void {
|
||||
for(auto& sizable : state().sizables) sizable->construct();
|
||||
}
|
||||
|
||||
auto pLayout::destruct() -> void {
|
||||
for(auto& sizable : state().sizables) sizable->destruct();
|
||||
}
|
||||
|
||||
auto pLayout::setEnabled(bool enabled) -> void {
|
||||
for(auto& sizable : state().sizables) {
|
||||
if(sizable->self()) sizable->self()->setEnabled(sizable->enabled(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pLayout::setFont(const string& font) -> void {
|
||||
for(auto& sizable : state().sizables) {
|
||||
if(sizable->self()) sizable->self()->setFont(sizable->font(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pLayout::setVisible(bool visible) -> void {
|
||||
for(auto& sizable : state().sizables) {
|
||||
if(sizable->self()) sizable->self()->setVisible(sizable->visible(true));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
hiro/gtk/layout.hpp
Normal file
11
hiro/gtk/layout.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pLayout : pSizable {
|
||||
Declare(Layout, Sizable)
|
||||
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
};
|
||||
|
||||
}
|
45
hiro/gtk/menu-bar.cpp
Normal file
45
hiro/gtk/menu-bar.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pMenuBar::construct() -> void {
|
||||
}
|
||||
|
||||
auto pMenuBar::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pMenuBar::append(shared_pointer<mMenu> menu) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_append(*menu);
|
||||
if(menu->self()) {
|
||||
menu->self()->setFont(menu->font(true));
|
||||
menu->self()->setVisible(menu->visible(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuBar::remove(shared_pointer<mMenu> menu) -> void {
|
||||
}
|
||||
|
||||
auto pMenuBar::setEnabled(bool enabled) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setMenuEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuBar::setFont(const string& font) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setMenuFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuBar::setVisible(bool visible) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setMenuVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
auto pMenuBar::_parent() -> pWindow* {
|
||||
if(auto parent = self().parentWindow()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
15
hiro/gtk/menu-bar.hpp
Normal file
15
hiro/gtk/menu-bar.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMenuBar : pObject {
|
||||
Declare(MenuBar, Object)
|
||||
|
||||
auto append(shared_pointer<mMenu> menu) -> void;
|
||||
auto remove(shared_pointer<mMenu> menu) -> void;
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto _parent() -> pWindow*;
|
||||
};
|
||||
|
||||
}
|
60
hiro/gtk/message-window.cpp
Normal file
60
hiro/gtk/message-window.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto Message(MessageWindow::State& state, GtkMessageType messageStyle) -> MessageWindow::Response {
|
||||
GtkWidget* dialog = gtk_message_dialog_new(
|
||||
state.parent && state.parent->self() ? GTK_WINDOW(state.parent->self()->widget) : (GtkWindow*)nullptr,
|
||||
GTK_DIALOG_MODAL, messageStyle, GTK_BUTTONS_NONE, "%s", (const char*)state.text
|
||||
);
|
||||
|
||||
if(state.title) gtk_window_set_title(GTK_WINDOW(dialog), state.title);
|
||||
else if(Application::state.name) gtk_window_set_title(GTK_WINDOW(dialog), Application::state.name);
|
||||
|
||||
switch(state.buttons) {
|
||||
case MessageWindow::Buttons::Ok:
|
||||
gtk_dialog_add_buttons(GTK_DIALOG(dialog), "Ok", GTK_RESPONSE_OK, nullptr);
|
||||
break;
|
||||
case MessageWindow::Buttons::OkCancel:
|
||||
gtk_dialog_add_buttons(GTK_DIALOG(dialog), "Ok", GTK_RESPONSE_OK, "Cancel", GTK_RESPONSE_CANCEL, nullptr);
|
||||
break;
|
||||
case MessageWindow::Buttons::YesNo:
|
||||
gtk_dialog_add_buttons(GTK_DIALOG(dialog), "Yes", GTK_RESPONSE_YES, "No", GTK_RESPONSE_NO, nullptr);
|
||||
break;
|
||||
case MessageWindow::Buttons::YesNoCancel:
|
||||
gtk_dialog_add_buttons(GTK_DIALOG(dialog), "Yes", GTK_RESPONSE_YES, "No", GTK_RESPONSE_NO, "Cancel", GTK_RESPONSE_CANCEL, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
auto response = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
|
||||
if(response == GTK_RESPONSE_OK) return MessageWindow::Response::Ok;
|
||||
if(response == GTK_RESPONSE_CANCEL) return MessageWindow::Response::Cancel;
|
||||
if(response == GTK_RESPONSE_YES) return MessageWindow::Response::Yes;
|
||||
if(response == GTK_RESPONSE_NO) return MessageWindow::Response::No;
|
||||
|
||||
//if dialog was closed without choosing a button, choose the most appropriate response
|
||||
if(state.buttons == MessageWindow::Buttons::Ok) return MessageWindow::Response::Ok;
|
||||
if(state.buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel;
|
||||
if(state.buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No;
|
||||
if(state.buttons == MessageWindow::Buttons::YesNoCancel) return MessageWindow::Response::Cancel;
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
auto pMessageWindow::error(MessageWindow::State& state) -> MessageWindow::Response {
|
||||
return Message(state, GTK_MESSAGE_ERROR);
|
||||
}
|
||||
|
||||
auto pMessageWindow::information(MessageWindow::State& state) -> MessageWindow::Response {
|
||||
return Message(state, GTK_MESSAGE_INFO);
|
||||
}
|
||||
|
||||
auto pMessageWindow::question(MessageWindow::State& state) -> MessageWindow::Response {
|
||||
return Message(state, GTK_MESSAGE_QUESTION);
|
||||
}
|
||||
|
||||
auto pMessageWindow::warning(MessageWindow::State& state) -> MessageWindow::Response {
|
||||
return Message(state, GTK_MESSAGE_WARNING);
|
||||
}
|
||||
|
||||
}
|
10
hiro/gtk/message-window.hpp
Normal file
10
hiro/gtk/message-window.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMessageWindow {
|
||||
static auto error(MessageWindow::State& state) -> MessageWindow::Response;
|
||||
static auto information(MessageWindow::State& state) -> MessageWindow::Response;
|
||||
static auto question(MessageWindow::State& state) -> MessageWindow::Response;
|
||||
static auto warning(MessageWindow::State& state) -> MessageWindow::Response;
|
||||
};
|
||||
|
||||
}
|
17
hiro/gtk/monitor.cpp
Normal file
17
hiro/gtk/monitor.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace hiro {
|
||||
|
||||
unsigned pMonitor::count() {
|
||||
return gdk_screen_get_n_monitors(gdk_screen_get_default());
|
||||
}
|
||||
|
||||
Geometry pMonitor::geometry(unsigned monitor) {
|
||||
GdkRectangle rectangle = {0};
|
||||
gdk_screen_get_monitor_geometry(gdk_screen_get_default(), monitor, &rectangle);
|
||||
return {rectangle.x, rectangle.y, rectangle.width, rectangle.height};
|
||||
}
|
||||
|
||||
unsigned pMonitor::primary() {
|
||||
return gdk_screen_get_primary_monitor(gdk_screen_get_default());
|
||||
}
|
||||
|
||||
}
|
9
hiro/gtk/monitor.hpp
Normal file
9
hiro/gtk/monitor.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMonitor {
|
||||
static unsigned count();
|
||||
static Geometry geometry(unsigned monitor);
|
||||
static unsigned primary();
|
||||
};
|
||||
|
||||
}
|
24
hiro/gtk/mouse.cpp
Normal file
24
hiro/gtk/mouse.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pMouse::position() -> Position {
|
||||
XlibWindow root, child;
|
||||
int rootx, rooty, winx, winy;
|
||||
unsigned int mask;
|
||||
XQueryPointer(pApplication::display, DefaultRootWindow(pApplication::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask);
|
||||
return {rootx, rooty};
|
||||
}
|
||||
|
||||
auto pMouse::pressed(Mouse::Button button) -> bool {
|
||||
XlibWindow root, child;
|
||||
int rootx, rooty, winx, winy;
|
||||
unsigned int mask;
|
||||
XQueryPointer(pApplication::display, DefaultRootWindow(pApplication::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask);
|
||||
switch(button) {
|
||||
case Mouse::Button::Left: return mask & Button1Mask;
|
||||
case Mouse::Button::Middle: return mask & Button2Mask;
|
||||
case Mouse::Button::Right: return mask & Button3Mask;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
8
hiro/gtk/mouse.hpp
Normal file
8
hiro/gtk/mouse.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pMouse {
|
||||
static auto position() -> Position;
|
||||
static auto pressed(Mouse::Button button) -> bool;
|
||||
};
|
||||
|
||||
}
|
31
hiro/gtk/object.cpp
Normal file
31
hiro/gtk/object.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pObject::construct() -> void {
|
||||
}
|
||||
|
||||
auto pObject::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pObject::focused() const -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pObject::remove() -> void {
|
||||
}
|
||||
|
||||
auto pObject::reset() -> void {
|
||||
}
|
||||
|
||||
auto pObject::setEnabled(bool enabled) -> void {
|
||||
}
|
||||
|
||||
auto pObject::setFocused() -> void {
|
||||
}
|
||||
|
||||
auto pObject::setFont(const string& font) -> void {
|
||||
}
|
||||
|
||||
auto pObject::setVisible(bool visible) -> void {
|
||||
}
|
||||
|
||||
}
|
27
hiro/gtk/object.hpp
Normal file
27
hiro/gtk/object.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pObject {
|
||||
pObject(mObject& reference) : reference(reference) {}
|
||||
virtual ~pObject() {}
|
||||
auto self() const -> mObject& { return (mObject&)reference; }
|
||||
auto state() const -> mObject::State& { return self().state; }
|
||||
virtual auto construct() -> void;
|
||||
virtual auto destruct() -> void;
|
||||
|
||||
virtual auto focused() const -> bool;
|
||||
virtual auto remove() -> void;
|
||||
virtual auto reset() -> void;
|
||||
virtual auto setEnabled(bool enabled) -> void;
|
||||
virtual auto setFocused() -> void;
|
||||
virtual auto setFont(const string& font) -> void;
|
||||
virtual auto setVisible(bool visible) -> void;
|
||||
|
||||
auto locked() const -> bool { return locks != 0; }
|
||||
auto lock() -> void { ++locks; }
|
||||
auto unlock() -> void { --locks; }
|
||||
|
||||
mObject& reference;
|
||||
signed locks = 0;
|
||||
};
|
||||
|
||||
}
|
62
hiro/gtk/platform.cpp
Normal file
62
hiro/gtk/platform.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "platform.hpp"
|
||||
#include "utility.cpp"
|
||||
|
||||
#include "font.cpp"
|
||||
#include "desktop.cpp"
|
||||
#include "monitor.cpp"
|
||||
#include "keyboard.cpp"
|
||||
#include "mouse.cpp"
|
||||
#include "browser-window.cpp"
|
||||
#include "message-window.cpp"
|
||||
#include "object.cpp"
|
||||
#include "hotkey.cpp"
|
||||
#include "timer.cpp"
|
||||
#include "window.cpp"
|
||||
#include "status-bar.cpp"
|
||||
#include "menu-bar.cpp"
|
||||
#include "popup-menu.cpp"
|
||||
|
||||
#include "action/action.cpp"
|
||||
#include "action/menu.cpp"
|
||||
#include "action/menu-separator.cpp"
|
||||
#include "action/menu-item.cpp"
|
||||
#include "action/menu-check-item.cpp"
|
||||
#include "action/menu-radio-item.cpp"
|
||||
|
||||
#include "sizable.cpp"
|
||||
#include "layout.cpp"
|
||||
|
||||
#include "widget/widget.cpp"
|
||||
#include "widget/button.cpp"
|
||||
#include "widget/canvas.cpp"
|
||||
#include "widget/check-button.cpp"
|
||||
#include "widget/check-label.cpp"
|
||||
#include "widget/combo-button.cpp"
|
||||
#include "widget/combo-button-item.cpp"
|
||||
#include "widget/console.cpp"
|
||||
#include "widget/frame.cpp"
|
||||
#include "widget/hex-edit.cpp"
|
||||
#include "widget/horizontal-scroller.cpp"
|
||||
#include "widget/horizontal-slider.cpp"
|
||||
#include "widget/icon-view.cpp"
|
||||
#include "widget/icon-view-item.cpp"
|
||||
#include "widget/label.cpp"
|
||||
#include "widget/line-edit.cpp"
|
||||
#include "widget/list-view.cpp"
|
||||
#include "widget/list-view-column.cpp"
|
||||
#include "widget/list-view-item.cpp"
|
||||
#include "widget/progress-bar.cpp"
|
||||
#include "widget/radio-button.cpp"
|
||||
#include "widget/radio-label.cpp"
|
||||
#include "widget/source-edit.cpp"
|
||||
#include "widget/tab-frame.cpp"
|
||||
#include "widget/tab-frame-item.cpp"
|
||||
#include "widget/text-edit.cpp"
|
||||
#include "widget/tree-view.cpp"
|
||||
#include "widget/tree-view-item.cpp"
|
||||
#include "widget/vertical-scroller.cpp"
|
||||
#include "widget/vertical-slider.cpp"
|
||||
#include "widget/viewport.cpp"
|
||||
|
||||
#include "application.cpp"
|
||||
#include "settings.cpp"
|
75
hiro/gtk/platform.hpp
Normal file
75
hiro/gtk/platform.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace hiro {
|
||||
struct pWindow;
|
||||
struct pMenu;
|
||||
struct pLayout;
|
||||
struct pWidget;
|
||||
};
|
||||
|
||||
#define Declare(Name, Base) \
|
||||
p##Name(m##Name& reference) : p##Base(reference) {} \
|
||||
auto self() const -> m##Name& { return (m##Name&)reference; } \
|
||||
auto state() const -> m##Name::State& { return self().state; } \
|
||||
auto construct() -> void override; \
|
||||
auto destruct() -> void override; \
|
||||
|
||||
#include "font.hpp"
|
||||
#include "desktop.hpp"
|
||||
#include "monitor.hpp"
|
||||
#include "keyboard.hpp"
|
||||
#include "mouse.hpp"
|
||||
#include "browser-window.hpp"
|
||||
#include "message-window.hpp"
|
||||
#include "object.hpp"
|
||||
#include "hotkey.hpp"
|
||||
#include "timer.hpp"
|
||||
#include "window.hpp"
|
||||
#include "status-bar.hpp"
|
||||
#include "menu-bar.hpp"
|
||||
#include "popup-menu.hpp"
|
||||
|
||||
#include "action/action.hpp"
|
||||
#include "action/menu.hpp"
|
||||
#include "action/menu-separator.hpp"
|
||||
#include "action/menu-item.hpp"
|
||||
#include "action/menu-check-item.hpp"
|
||||
#include "action/menu-radio-item.hpp"
|
||||
|
||||
#include "sizable.hpp"
|
||||
#include "layout.hpp"
|
||||
|
||||
#include "widget/widget.hpp"
|
||||
#include "widget/button.hpp"
|
||||
#include "widget/canvas.hpp"
|
||||
#include "widget/check-button.hpp"
|
||||
#include "widget/check-label.hpp"
|
||||
#include "widget/combo-button.hpp"
|
||||
#include "widget/combo-button-item.hpp"
|
||||
#include "widget/console.hpp"
|
||||
#include "widget/frame.hpp"
|
||||
#include "widget/hex-edit.hpp"
|
||||
#include "widget/horizontal-scroller.hpp"
|
||||
#include "widget/horizontal-slider.hpp"
|
||||
#include "widget/icon-view.hpp"
|
||||
#include "widget/icon-view-item.hpp"
|
||||
#include "widget/label.hpp"
|
||||
#include "widget/line-edit.hpp"
|
||||
#include "widget/list-view.hpp"
|
||||
#include "widget/list-view-column.hpp"
|
||||
#include "widget/list-view-item.hpp"
|
||||
#include "widget/progress-bar.hpp"
|
||||
#include "widget/radio-button.hpp"
|
||||
#include "widget/radio-label.hpp"
|
||||
#include "widget/source-edit.hpp"
|
||||
#include "widget/tab-frame.hpp"
|
||||
#include "widget/tab-frame-item.hpp"
|
||||
#include "widget/text-edit.hpp"
|
||||
#include "widget/tree-view.hpp"
|
||||
#include "widget/tree-view-item.hpp"
|
||||
#include "widget/vertical-scroller.hpp"
|
||||
#include "widget/vertical-slider.hpp"
|
||||
#include "widget/viewport.hpp"
|
||||
|
||||
#undef Declare
|
||||
|
||||
#include "application.hpp"
|
||||
#include "settings.hpp"
|
32
hiro/gtk/popup-menu.cpp
Normal file
32
hiro/gtk/popup-menu.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pPopupMenu::construct() -> void {
|
||||
gtkMenu = gtk_menu_new();
|
||||
}
|
||||
|
||||
auto pPopupMenu::destruct() -> void {
|
||||
gtk_widget_destroy(gtkMenu);
|
||||
}
|
||||
|
||||
auto pPopupMenu::append(sAction action) -> void {
|
||||
if(action->self()) {
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), action->self()->widget);
|
||||
action->self()->setFont(action->font(true));
|
||||
action->self()->setVisible(action->visible(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pPopupMenu::remove(sAction action) -> void {
|
||||
}
|
||||
|
||||
auto pPopupMenu::setFont(const string& font) -> void {
|
||||
for(auto& action : state().actions) {
|
||||
if(action->self()) action->self()->setFont(action->font(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pPopupMenu::setVisible(bool visible) -> void {
|
||||
if(visible) gtk_menu_popup(GTK_MENU(gtkMenu), nullptr, nullptr, nullptr, nullptr, 0, gtk_get_current_event_time());
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/popup-menu.hpp
Normal file
14
hiro/gtk/popup-menu.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pPopupMenu : pObject {
|
||||
Declare(PopupMenu, Object)
|
||||
|
||||
auto append(sAction action) -> void;
|
||||
auto remove(sAction action) -> void;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setVisible(bool visible) -> void;
|
||||
|
||||
GtkWidget* gtkMenu = nullptr;
|
||||
};
|
||||
|
||||
}
|
26
hiro/gtk/settings.cpp
Normal file
26
hiro/gtk/settings.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace hiro {
|
||||
|
||||
void Settings::load() {
|
||||
string path = {userpath(), ".config/phoenix/"};
|
||||
Configuration::Document::load({path, "gtk.bml"});
|
||||
}
|
||||
|
||||
void Settings::save() {
|
||||
string path = {userpath(), ".config/phoenix/"};
|
||||
directory::create(path, 0755);
|
||||
Configuration::Document::save({path, "gtk.bml"});
|
||||
}
|
||||
|
||||
Settings::Settings() {
|
||||
geometry.append(geometry.frameX = 4, "FrameX");
|
||||
geometry.append(geometry.frameY = 24, "FrameY");
|
||||
geometry.append(geometry.frameWidth = 8, "FrameWidth");
|
||||
geometry.append(geometry.frameHeight = 28, "FrameHeight");
|
||||
geometry.append(geometry.menuHeight = 20, "MenuHeight");
|
||||
geometry.append(geometry.statusHeight = 20, "StatusHeight");
|
||||
append(geometry, "Geometry");
|
||||
window.append(window.backgroundColor = 0xedeceb, "BackgroundColor");
|
||||
append(window, "Window");
|
||||
}
|
||||
|
||||
}
|
26
hiro/gtk/settings.hpp
Normal file
26
hiro/gtk/settings.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace hiro {
|
||||
|
||||
struct Settings : Configuration::Document {
|
||||
vector<uint16_t> keycodes;
|
||||
|
||||
struct Geometry : Configuration::Node {
|
||||
signed frameX;
|
||||
signed frameY;
|
||||
signed frameWidth;
|
||||
signed frameHeight;
|
||||
signed menuHeight;
|
||||
signed statusHeight;
|
||||
} geometry;
|
||||
|
||||
struct Window : Configuration::Node {
|
||||
unsigned backgroundColor;
|
||||
} window;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
Settings();
|
||||
};
|
||||
|
||||
static Settings* settings = nullptr;
|
||||
|
||||
}
|
16
hiro/gtk/sizable.cpp
Normal file
16
hiro/gtk/sizable.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pSizable::construct() -> void {
|
||||
}
|
||||
|
||||
auto pSizable::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pSizable::minimumSize() const -> Size {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
auto pSizable::setGeometry(Geometry geometry) -> void {
|
||||
}
|
||||
|
||||
}
|
10
hiro/gtk/sizable.hpp
Normal file
10
hiro/gtk/sizable.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pSizable : pObject {
|
||||
Declare(Sizable, Object)
|
||||
|
||||
virtual auto minimumSize() const -> Size;
|
||||
virtual auto setGeometry(Geometry geometry) -> void;
|
||||
};
|
||||
|
||||
}
|
38
hiro/gtk/status-bar.cpp
Normal file
38
hiro/gtk/status-bar.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pStatusBar::construct() -> void {
|
||||
}
|
||||
|
||||
auto pStatusBar::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pStatusBar::setEnabled(bool enabled) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setStatusEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
auto pStatusBar::setFont(const string& font) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setStatusFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
auto pStatusBar::setText(const string& text) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setStatusText(text);
|
||||
}
|
||||
}
|
||||
|
||||
auto pStatusBar::setVisible(bool visible) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->_setStatusVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
auto pStatusBar::_parent() -> pWindow* {
|
||||
if(auto parent = self().parentWindow()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/status-bar.hpp
Normal file
14
hiro/gtk/status-bar.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pStatusBar : pObject {
|
||||
Declare(StatusBar, Object)
|
||||
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setText(const string& text) -> void;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto _parent() -> pWindow*;
|
||||
};
|
||||
|
||||
}
|
31
hiro/gtk/timer.cpp
Normal file
31
hiro/gtk/timer.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto Timer_trigger(pTimer* p) -> signed {
|
||||
//timer may have been disabled prior to triggering, so check state
|
||||
if(p->self().enabled(true)) p->self().doActivate();
|
||||
|
||||
//callback may have disabled timer, so check state again
|
||||
if(p->self().enabled(true)) {
|
||||
g_timeout_add(p->state().interval, (GSourceFunc)Timer_trigger, (gpointer)p);
|
||||
}
|
||||
|
||||
//kill this timer instance (it is spawned above if needed again)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pTimer::construct() -> void {
|
||||
}
|
||||
|
||||
auto pTimer::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pTimer::setEnabled(bool enabled) -> void {
|
||||
if(enabled) {
|
||||
g_timeout_add(state().interval, (GSourceFunc)Timer_trigger, (gpointer)this);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTimer::setInterval(unsigned interval) -> void {
|
||||
}
|
||||
|
||||
}
|
10
hiro/gtk/timer.hpp
Normal file
10
hiro/gtk/timer.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pTimer : pObject {
|
||||
Declare(Timer, Object)
|
||||
|
||||
auto setEnabled(bool enabled) -> void;
|
||||
auto setInterval(unsigned interval) -> void;
|
||||
};
|
||||
|
||||
}
|
49
hiro/gtk/utility.cpp
Normal file
49
hiro/gtk/utility.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto CreateColor(const Color& color) -> GdkColor {
|
||||
GdkColor gdkColor;
|
||||
gdkColor.pixel = (color.red() << 16) | (color.green() << 8) | (color.blue() << 0);
|
||||
gdkColor.red = (color.red() << 8) | (color.red() << 0);
|
||||
gdkColor.green = (color.green() << 8) | (color.green() << 0);
|
||||
gdkColor.blue = (color.blue() << 8) | (color.blue() << 0);
|
||||
return gdkColor;
|
||||
}
|
||||
|
||||
static auto CreatePixbuf(const nall::image& image, bool scale = false) -> GdkPixbuf* {
|
||||
nall::image gdkImage = image;
|
||||
gdkImage.transform(0, 32, 255u << 24, 255u << 0, 255u << 8, 255u << 16);
|
||||
if(scale) gdkImage.scale(15, 15);
|
||||
|
||||
GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, gdkImage.width, gdkImage.height);
|
||||
memcpy(gdk_pixbuf_get_pixels(pixbuf), gdkImage.data, gdkImage.width * gdkImage.height * 4);
|
||||
|
||||
return pixbuf;
|
||||
}
|
||||
|
||||
static auto CreateImage(const nall::image& image, bool scale = false) -> GtkImage* {
|
||||
GdkPixbuf* pixbuf = CreatePixbuf(image, scale);
|
||||
GtkImage* gtkImage = (GtkImage*)gtk_image_new_from_pixbuf(pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
return gtkImage;
|
||||
}
|
||||
|
||||
static auto DropPaths(GtkSelectionData* data) -> lstring {
|
||||
gchar** uris = gtk_selection_data_get_uris(data);
|
||||
if(uris == nullptr) return {};
|
||||
|
||||
lstring paths;
|
||||
for(unsigned n = 0; uris[n] != nullptr; n++) {
|
||||
gchar* pathname = g_filename_from_uri(uris[n], nullptr, nullptr);
|
||||
if(pathname == nullptr) continue;
|
||||
|
||||
string path = pathname;
|
||||
g_free(pathname);
|
||||
if(directory::exists(path) && !path.endsWith("/")) path.append("/");
|
||||
paths.append(path);
|
||||
}
|
||||
|
||||
g_strfreev(uris);
|
||||
return paths;
|
||||
}
|
||||
|
||||
}
|
68
hiro/gtk/widget/button.cpp
Normal file
68
hiro/gtk/widget/button.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto Button_activate(GtkButton* gtkButton, pButton* p) -> void { p->_doActivate(); }
|
||||
|
||||
auto pButton::construct() -> void {
|
||||
gtkWidget = gtk_button_new();
|
||||
gtkButton = GTK_BUTTON(gtkWidget);
|
||||
|
||||
setBordered(state().bordered);
|
||||
setIcon(state().icon);
|
||||
setOrientation(state().orientation);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkButton), "clicked", G_CALLBACK(Button_activate), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pButton::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pButton::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
|
||||
if(state().orientation == Orientation::Horizontal) {
|
||||
size.setWidth(size.width() + state().icon.width);
|
||||
size.setHeight(max(size.height(), state().icon.height));
|
||||
}
|
||||
|
||||
if(state().orientation == Orientation::Vertical) {
|
||||
size.setWidth(max(size.width(), state().icon.width));
|
||||
size.setHeight(size.height() + state().icon.height);
|
||||
}
|
||||
|
||||
return {size.width() + (state().text ? 24 : 12), size.height() + 12};
|
||||
}
|
||||
|
||||
auto pButton::setBordered(bool bordered) -> void {
|
||||
gtk_button_set_relief(gtkButton, bordered ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
|
||||
}
|
||||
|
||||
auto pButton::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
auto gtkImage = CreateImage(icon);
|
||||
gtk_button_set_image(gtkButton, (GtkWidget*)gtkImage);
|
||||
} else {
|
||||
gtk_button_set_image(gtkButton, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pButton::setOrientation(Orientation orientation) -> void {
|
||||
switch(orientation) {
|
||||
case Orientation::Horizontal: gtk_button_set_image_position(gtkButton, GTK_POS_LEFT); break;
|
||||
case Orientation::Vertical: gtk_button_set_image_position(gtkButton, GTK_POS_TOP); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto pButton::setText(const string& text) -> void {
|
||||
gtk_button_set_label(gtkButton, text);
|
||||
setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font
|
||||
}
|
||||
|
||||
auto pButton::_doActivate() -> void {
|
||||
self().doActivate();
|
||||
}
|
||||
|
||||
}
|
17
hiro/gtk/widget/button.hpp
Normal file
17
hiro/gtk/widget/button.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pButton : pWidget {
|
||||
Declare(Button, Widget)
|
||||
|
||||
auto minimumSize() const -> Size override;
|
||||
auto setBordered(bool bordered) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setOrientation(Orientation orientation) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _doActivate() -> void;
|
||||
|
||||
GtkButton* gtkButton = nullptr;
|
||||
};
|
||||
|
||||
}
|
212
hiro/gtk/widget/canvas.cpp
Normal file
212
hiro/gtk/widget/canvas.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto Canvas_drop(GtkWidget* widget, GdkDragContext* context, signed x, signed y,
|
||||
GtkSelectionData* data, unsigned type, unsigned timestamp, pCanvas* p) -> void {
|
||||
if(!p->state().droppable) return;
|
||||
lstring paths = DropPaths(data);
|
||||
if(paths.empty()) return;
|
||||
p->self().doDrop(paths);
|
||||
}
|
||||
|
||||
static auto Canvas_expose(GtkWidget* widget, GdkEventExpose* event, pCanvas* p) -> signed {
|
||||
p->_onExpose(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto Canvas_mouseLeave(GtkWidget* widget, GdkEventButton* event, pCanvas* p) -> signed {
|
||||
p->self().doMouseLeave();
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto Canvas_mouseMove(GtkWidget* widget, GdkEventButton* event, pCanvas* p) -> signed {
|
||||
p->self().doMouseMove({(signed)event->x, (signed)event->y});
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto Canvas_mousePress(GtkWidget* widget, GdkEventButton* event, pCanvas* p) -> signed {
|
||||
switch(event->button) {
|
||||
case 1: p->self().doMousePress(Mouse::Button::Left); break;
|
||||
case 2: p->self().doMousePress(Mouse::Button::Middle); break;
|
||||
case 3: p->self().doMousePress(Mouse::Button::Right); break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto Canvas_mouseRelease(GtkWidget* widget, GdkEventButton* event, pCanvas* p) -> signed {
|
||||
switch(event->button) {
|
||||
case 1: p->self().doMouseRelease(Mouse::Button::Left); break;
|
||||
case 2: p->self().doMouseRelease(Mouse::Button::Middle); break;
|
||||
case 3: p->self().doMouseRelease(Mouse::Button::Right); break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto pCanvas::construct() -> void {
|
||||
gtkWidget = gtk_drawing_area_new();
|
||||
gtk_widget_add_events(gtkWidget,
|
||||
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK);
|
||||
|
||||
setDroppable(state().droppable);
|
||||
_rasterize();
|
||||
_redraw();
|
||||
|
||||
//todo: need to work around GTK+ library bug:
|
||||
//after calling destruct(), construct() with state.droppable == true;
|
||||
//GTK+ will throw SIGBUS inside g_signal_connect_data() on one of the below connections
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "button-press-event", G_CALLBACK(Canvas_mousePress), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "button-release-event", G_CALLBACK(Canvas_mouseRelease), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "drag-data-received", G_CALLBACK(Canvas_drop), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "expose-event", G_CALLBACK(Canvas_expose), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "leave-notify-event", G_CALLBACK(Canvas_mouseLeave), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "motion-notify-event", G_CALLBACK(Canvas_mouseMove), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pCanvas::destruct() -> void {
|
||||
_release();
|
||||
if(gtkWidget) gtk_widget_destroy(gtkWidget), gtkWidget = nullptr;
|
||||
gtkParent = nullptr;
|
||||
}
|
||||
|
||||
auto pCanvas::minimumSize() const -> Size {
|
||||
return {max(0, state().size.width()), max(0, state().size.height())};
|
||||
}
|
||||
|
||||
auto pCanvas::setColor(Color color) -> void {
|
||||
mode = Mode::Color;
|
||||
update();
|
||||
}
|
||||
|
||||
auto pCanvas::setData(Size size) -> void {
|
||||
mode = Mode::Data;
|
||||
update();
|
||||
}
|
||||
|
||||
auto pCanvas::setDroppable(bool droppable) -> void {
|
||||
if(droppable) {
|
||||
gtk_drag_dest_set(gtkWidget, GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY);
|
||||
gtk_drag_dest_add_uri_targets(gtkWidget);
|
||||
}
|
||||
}
|
||||
|
||||
auto pCanvas::setGeometry(Geometry geometry) -> void {
|
||||
update();
|
||||
pWidget::setGeometry(geometry);
|
||||
}
|
||||
|
||||
auto pCanvas::setGradient(Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) -> void {
|
||||
mode = Mode::Gradient;
|
||||
update();
|
||||
}
|
||||
|
||||
auto pCanvas::setIcon(const image& icon) -> void {
|
||||
mode = Mode::Icon;
|
||||
update();
|
||||
}
|
||||
|
||||
auto pCanvas::update() -> void {
|
||||
_rasterize();
|
||||
_redraw();
|
||||
}
|
||||
|
||||
auto pCanvas::_onExpose(GdkEventExpose* expose) -> void {
|
||||
if(surface == nullptr) return;
|
||||
|
||||
signed sx = 0, sy = 0, dx = 0, dy = 0;
|
||||
signed width = surfaceWidth;
|
||||
signed height = surfaceHeight;
|
||||
auto geometry = pSizable::state().geometry;
|
||||
|
||||
if(width <= geometry.width()) {
|
||||
sx = 0;
|
||||
dx = (geometry.width() - width) / 2;
|
||||
} else {
|
||||
sx = (width - geometry.width()) / 2;
|
||||
dx = 0;
|
||||
width = geometry.width();
|
||||
}
|
||||
|
||||
if(height <= geometry.height()) {
|
||||
sy = 0;
|
||||
dy = (geometry.height() - height) / 2;
|
||||
} else {
|
||||
sy = (height - geometry.height()) / 2;
|
||||
dy = 0;
|
||||
height = geometry.height();
|
||||
}
|
||||
|
||||
gdk_draw_pixbuf(gtk_widget_get_window(gtkWidget), nullptr, surface, sx, sy, dx, dy, width, height, GDK_RGB_DITHER_NONE, 0, 0);
|
||||
}
|
||||
|
||||
auto pCanvas::_rasterize() -> void {
|
||||
signed width = 0;
|
||||
signed height = 0;
|
||||
|
||||
if(mode == Mode::Color || mode == Mode::Gradient) {
|
||||
width = pSizable::state().geometry.width();
|
||||
height = pSizable::state().geometry.height();
|
||||
} else {
|
||||
width = state().size.width();
|
||||
height = state().size.height();
|
||||
}
|
||||
|
||||
if(width <= 0 || height <= 0) return;
|
||||
|
||||
if(width != surfaceWidth || height != surfaceHeight) _release();
|
||||
surfaceWidth = width;
|
||||
surfaceHeight = height;
|
||||
|
||||
if(!surface) surface = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, width, height);
|
||||
uint32_t* buffer = (uint32_t*)gdk_pixbuf_get_pixels(surface);
|
||||
|
||||
if(mode == Mode::Color) {
|
||||
uint32_t color = state().color.value();
|
||||
for(auto n : range(width * height)) buffer[n] = color;
|
||||
}
|
||||
|
||||
if(mode == Mode::Gradient) {
|
||||
image fill;
|
||||
fill.allocate(width, height);
|
||||
fill.gradient(
|
||||
state().gradient[0].value(), state().gradient[1].value(), state().gradient[2].value(), state().gradient[3].value()
|
||||
);
|
||||
memory::copy(buffer, fill.data, fill.size);
|
||||
}
|
||||
|
||||
if(mode == Mode::Icon) {
|
||||
auto icon = state().icon;
|
||||
icon.scale(width, height);
|
||||
icon.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0);
|
||||
memory::copy(buffer, icon.data, icon.size);
|
||||
}
|
||||
|
||||
if(mode == Mode::Data) {
|
||||
memory::copy(buffer, state().data.data(), state().data.size() * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
//ARGB -> ABGR conversion
|
||||
for(auto n : range(width * height)) {
|
||||
uint32_t color = *buffer;
|
||||
color = (color & 0xff00ff00) | ((color & 0xff0000) >> 16) | ((color & 0x0000ff) << 16);
|
||||
*buffer++ = color;
|
||||
}
|
||||
}
|
||||
|
||||
auto pCanvas::_redraw() -> void {
|
||||
if(gtk_widget_get_realized(gtkWidget)) {
|
||||
gdk_window_invalidate_rect(gtk_widget_get_window(gtkWidget), nullptr, true);
|
||||
}
|
||||
}
|
||||
|
||||
auto pCanvas::_release() -> void {
|
||||
if(surface) {
|
||||
g_object_unref(surface);
|
||||
surface = nullptr;
|
||||
}
|
||||
surfaceWidth = 0;
|
||||
surfaceHeight = 0;
|
||||
}
|
||||
|
||||
}
|
28
hiro/gtk/widget/canvas.hpp
Normal file
28
hiro/gtk/widget/canvas.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pCanvas : pWidget {
|
||||
Declare(Canvas, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setColor(Color color) -> void;
|
||||
auto setData(Size size) -> void;
|
||||
auto setDroppable(bool droppable) -> void;
|
||||
auto setGeometry(Geometry geometry) -> void override;
|
||||
auto setGradient(Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto update() -> void;
|
||||
|
||||
enum class Mode : unsigned { Color, Data, Gradient, Icon };
|
||||
|
||||
auto _onExpose(GdkEventExpose* event) -> void;
|
||||
auto _rasterize() -> void;
|
||||
auto _redraw() -> void;
|
||||
auto _release() -> void;
|
||||
|
||||
GdkPixbuf* surface = nullptr;
|
||||
unsigned surfaceWidth = 0;
|
||||
unsigned surfaceHeight = 0;
|
||||
Mode mode = Mode::Color;
|
||||
};
|
||||
|
||||
}
|
73
hiro/gtk/widget/check-button.cpp
Normal file
73
hiro/gtk/widget/check-button.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto CheckButton_toggle(GtkToggleButton* toggleButton, pCheckButton* p) -> void {
|
||||
p->state().checked = gtk_toggle_button_get_active(toggleButton);
|
||||
if(!p->locked()) p->self().doToggle();
|
||||
}
|
||||
|
||||
auto pCheckButton::construct() -> void {
|
||||
gtkWidget = gtk_toggle_button_new();
|
||||
|
||||
setBordered(state().bordered);
|
||||
setChecked(state().checked);
|
||||
setIcon(state().icon);
|
||||
setOrientation(state().orientation);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(CheckButton_toggle), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pCheckButton::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pCheckButton::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
|
||||
if(state().orientation == Orientation::Horizontal) {
|
||||
size.setWidth(size.width() + state().icon.width);
|
||||
size.setHeight(max(size.height(), state().icon.height));
|
||||
}
|
||||
|
||||
if(state().orientation == Orientation::Vertical) {
|
||||
size.setWidth(max(size.width(), state().icon.width));
|
||||
size.setHeight(size.height() + state().icon.height);
|
||||
}
|
||||
|
||||
return {size.width() + 24, size.height() + 12};
|
||||
}
|
||||
|
||||
auto pCheckButton::setBordered(bool bordered) -> void {
|
||||
gtk_button_set_relief(GTK_BUTTON(gtkWidget), bordered ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
|
||||
}
|
||||
|
||||
auto pCheckButton::setChecked(bool checked) -> void {
|
||||
lock();
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), checked);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pCheckButton::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
GtkImage* gtkImage = CreateImage(icon);
|
||||
gtk_button_set_image(GTK_BUTTON(gtkWidget), (GtkWidget*)gtkImage);
|
||||
} else {
|
||||
gtk_button_set_image(GTK_BUTTON(gtkWidget), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pCheckButton::setOrientation(Orientation orientation) -> void {
|
||||
switch(orientation) {
|
||||
case Orientation::Horizontal: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_LEFT); break;
|
||||
case Orientation::Vertical: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_TOP); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto pCheckButton::setText(const string& text) -> void {
|
||||
gtk_button_set_label(GTK_BUTTON(gtkWidget), text);
|
||||
setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/widget/check-button.hpp
Normal file
14
hiro/gtk/widget/check-button.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pCheckButton : pWidget {
|
||||
Declare(CheckButton, Widget)
|
||||
|
||||
auto minimumSize() const -> Size override;
|
||||
auto setBordered(bool bordered) -> void;
|
||||
auto setChecked(bool checked) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setOrientation(Orientation orientation) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
};
|
||||
|
||||
}
|
39
hiro/gtk/widget/check-label.cpp
Normal file
39
hiro/gtk/widget/check-label.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto CheckLabel_toggle(GtkToggleButton* toggleButton, pCheckLabel* p) -> void {
|
||||
p->state().checked = gtk_toggle_button_get_active(toggleButton);
|
||||
if(!p->locked()) p->self().doToggle();
|
||||
}
|
||||
|
||||
auto pCheckLabel::construct() -> void {
|
||||
gtkWidget = gtk_check_button_new_with_label("");
|
||||
|
||||
setChecked(state().checked);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(CheckLabel_toggle), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pCheckLabel::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pCheckLabel::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
return {size.width() + 28, size.height() + 4};
|
||||
}
|
||||
|
||||
auto pCheckLabel::setChecked(bool checked) -> void {
|
||||
lock();
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), checked);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pCheckLabel::setText(const string& text) -> void {
|
||||
gtk_button_set_label(GTK_BUTTON(gtkWidget), text);
|
||||
setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font
|
||||
}
|
||||
|
||||
}
|
11
hiro/gtk/widget/check-label.hpp
Normal file
11
hiro/gtk/widget/check-label.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pCheckLabel : pWidget {
|
||||
Declare(CheckLabel, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setChecked(bool checked) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
};
|
||||
|
||||
}
|
42
hiro/gtk/widget/combo-button-item.cpp
Normal file
42
hiro/gtk/widget/combo-button-item.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pComboButtonItem::construct() -> void {
|
||||
}
|
||||
|
||||
auto pComboButtonItem::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pComboButtonItem::setIcon(const image& icon) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
if(icon) {
|
||||
auto copy = icon;
|
||||
auto size = pFont::size(self().font(true), " ").height();
|
||||
copy.scale(size, size);
|
||||
auto pixbuf = CreatePixbuf(copy);
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 0, pixbuf, -1);
|
||||
} else {
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 0, nullptr, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pComboButtonItem::setSelected() -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->lock();
|
||||
gtk_combo_box_set_active(parent->gtkComboBox, self().offset());
|
||||
parent->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
auto pComboButtonItem::setText(const string& text) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 1, text.data(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
auto pComboButtonItem::_parent() -> pComboButton* {
|
||||
if(auto parent = self().parentComboButton()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
15
hiro/gtk/widget/combo-button-item.hpp
Normal file
15
hiro/gtk/widget/combo-button-item.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pComboButtonItem : pObject {
|
||||
Declare(ComboButtonItem, Object)
|
||||
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setSelected() -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _parent() -> pComboButton*;
|
||||
|
||||
GtkTreeIter gtkIter;
|
||||
};
|
||||
|
||||
}
|
91
hiro/gtk/widget/combo-button.cpp
Normal file
91
hiro/gtk/widget/combo-button.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto ComboButton_change(GtkComboBox* comboBox, pComboButton* p) -> void { p->_updateSelected(); }
|
||||
|
||||
auto pComboButton::construct() -> void {
|
||||
gtkListStore = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
||||
gtkTreeModel = GTK_TREE_MODEL(gtkListStore);
|
||||
gtkWidget = gtk_combo_box_new_with_model(gtkTreeModel);
|
||||
gtkComboBox = GTK_COMBO_BOX(gtkWidget);
|
||||
|
||||
gtkCellIcon = gtk_cell_renderer_pixbuf_new();
|
||||
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkWidget), gtkCellIcon, false);
|
||||
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkWidget), gtkCellIcon, "pixbuf", 0, nullptr);
|
||||
gtkCellText = gtk_cell_renderer_text_new();
|
||||
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkWidget), gtkCellText, true);
|
||||
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkWidget), gtkCellText, "text", 1, nullptr);
|
||||
|
||||
for(auto& item : state().items) {
|
||||
append(item);
|
||||
if(item->selected()) item->setSelected();
|
||||
}
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "changed", G_CALLBACK(ComboButton_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pComboButton::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pComboButton::append(sComboButtonItem item) -> void {
|
||||
lock();
|
||||
if(auto delegate = item->self()) {
|
||||
gtk_list_store_append(gtkListStore, &delegate->gtkIter);
|
||||
item->setIcon(item->state.icon);
|
||||
item->setText(item->state.text);
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pComboButton::minimumSize() const -> Size {
|
||||
string font = self().font(true);
|
||||
signed maximumWidth = 0;
|
||||
for(auto& item : state().items) {
|
||||
maximumWidth = max(maximumWidth,
|
||||
(item->state.icon ? 2 + pFont::size(font, " ").height() : 0)
|
||||
+ pFont::size(font, item->state.text).width()
|
||||
);
|
||||
}
|
||||
Size size = pFont::size(font, " ");
|
||||
return {maximumWidth + 40, size.height() + 12};
|
||||
}
|
||||
|
||||
auto pComboButton::remove(sComboButtonItem item) -> void {
|
||||
lock();
|
||||
if(auto delegate = item->self()) {
|
||||
gtk_list_store_remove(gtkListStore, &delegate->gtkIter);
|
||||
//if the currently selected item is removed; GTK+ deselects everything
|
||||
//detect this behavior and select the first item instead of nothing
|
||||
if(gtk_combo_box_get_active(GTK_COMBO_BOX(gtkWidget)) < 0) {
|
||||
if(gtk_tree_model_iter_n_children(gtkTreeModel, nullptr) > 0) {
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), 0);
|
||||
state().selected = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pComboButton::reset() -> void {
|
||||
lock();
|
||||
gtk_list_store_clear(gtkListStore);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pComboButton::setFont(const string& font) -> void {
|
||||
pWidget::setFont(font);
|
||||
auto fontDescription = pFont::create(font);
|
||||
g_object_set(G_OBJECT(gtkCellText), "font-desc", fontDescription, nullptr);
|
||||
}
|
||||
|
||||
auto pComboButton::_updateSelected() -> void {
|
||||
signed selected = gtk_combo_box_get_active(gtkComboBox);
|
||||
if(selected >= 0) {
|
||||
state().selected = selected;
|
||||
if(!locked()) self().doChange();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
21
hiro/gtk/widget/combo-button.hpp
Normal file
21
hiro/gtk/widget/combo-button.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pComboButton : pWidget {
|
||||
Declare(ComboButton, Widget)
|
||||
|
||||
auto append(sComboButtonItem item) -> void;
|
||||
auto minimumSize() const -> Size override;
|
||||
auto remove(sComboButtonItem item) -> void;
|
||||
auto reset() -> void;
|
||||
auto setFont(const string& font) -> void override;
|
||||
|
||||
auto _updateSelected() -> void;
|
||||
|
||||
GtkListStore* gtkListStore = nullptr;
|
||||
GtkTreeModel* gtkTreeModel = nullptr;
|
||||
GtkComboBox* gtkComboBox = nullptr;
|
||||
GtkCellRenderer* gtkCellIcon = nullptr;
|
||||
GtkCellRenderer* gtkCellText = nullptr;
|
||||
};
|
||||
|
||||
}
|
185
hiro/gtk/widget/console.cpp
Normal file
185
hiro/gtk/widget/console.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto Console_keyPress(GtkWidget*, GdkEventKey* event, pConsole* p) -> signed {
|
||||
return p->_keyPress(event->keyval, event->state);
|
||||
}
|
||||
|
||||
auto pConsole::construct() -> void {
|
||||
gtkWidget = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
subWidget = gtk_text_view_new();
|
||||
gtk_widget_show(subWidget);
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), false);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_NONE);
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget);
|
||||
|
||||
textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget));
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
|
||||
g_signal_connect(G_OBJECT(subWidget), "key-press-event", G_CALLBACK(Console_keyPress), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pConsole::destruct() -> void {
|
||||
gtk_widget_destroy(subWidget);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pConsole::print(const string& text) -> void {
|
||||
//insert text before prompt and command
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_iter_at_line_offset(textBuffer, &iter, gtk_text_buffer_get_line_count(textBuffer), 0);
|
||||
gtk_text_buffer_insert(textBuffer, &iter, text, -1);
|
||||
_seekToEnd();
|
||||
}
|
||||
|
||||
auto pConsole::reset() -> void {
|
||||
//flush history and redraw prompt
|
||||
gtk_text_buffer_set_text(textBuffer, state().prompt, -1);
|
||||
_seekToEnd();
|
||||
}
|
||||
|
||||
auto pConsole::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pConsole::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pConsole::setPrompt(const string& prompt) -> void {
|
||||
//erase previous prompt and replace it with new prompt
|
||||
GtkTextIter lhs, rhs;
|
||||
gtk_text_buffer_get_iter_at_line_offset(textBuffer, &lhs, gtk_text_buffer_get_line_count(textBuffer), 0);
|
||||
gtk_text_buffer_get_iter_at_line_offset(textBuffer, &rhs, gtk_text_buffer_get_line_count(textBuffer), previousPrompt.size());
|
||||
gtk_text_buffer_delete(textBuffer, &lhs, &rhs);
|
||||
gtk_text_buffer_get_iter_at_line_offset(textBuffer, &lhs, gtk_text_buffer_get_line_count(textBuffer), 0);
|
||||
gtk_text_buffer_insert(textBuffer, &lhs, previousPrompt = prompt, -1);
|
||||
_seekToEnd();
|
||||
}
|
||||
|
||||
auto pConsole::_keyPress(unsigned scancode, unsigned mask) -> bool {
|
||||
if(mask & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK)) return false; //allow actions such as Ctrl+C (copy)
|
||||
|
||||
GtkTextMark* mark = gtk_text_buffer_get_mark(textBuffer, "insert");
|
||||
GtkTextIter start, cursor, end;
|
||||
gtk_text_buffer_get_iter_at_line_offset(textBuffer, &start, gtk_text_buffer_get_line_count(textBuffer), state().prompt.size());
|
||||
gtk_text_buffer_get_iter_at_mark(textBuffer, &cursor, mark);
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &end);
|
||||
|
||||
if(scancode == GDK_KEY_Return || scancode == GDK_KEY_KP_Enter) {
|
||||
char* temp = gtk_text_buffer_get_text(textBuffer, &start, &end, true);
|
||||
string s = temp;
|
||||
g_free(temp);
|
||||
gtk_text_buffer_insert(textBuffer, &end, string{"\n", state().prompt}, -1);
|
||||
self().doActivate(s);
|
||||
if(s) history.prepend(s);
|
||||
if(history.size() > 128) history.removeLast();
|
||||
historyOffset = 0;
|
||||
_seekToEnd();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Up) {
|
||||
gtk_text_buffer_delete(textBuffer, &start, &end);
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &end);
|
||||
if(historyOffset < history.size()) {
|
||||
gtk_text_buffer_insert(textBuffer, &end, history[historyOffset++], -1);
|
||||
}
|
||||
_seekToEnd();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Down) {
|
||||
gtk_text_buffer_delete(textBuffer, &start, &end);
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &end);
|
||||
if(historyOffset > 0) {
|
||||
gtk_text_buffer_insert(textBuffer, &end, history[--historyOffset], -1);
|
||||
}
|
||||
_seekToEnd();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Left) {
|
||||
if(gtk_text_iter_get_offset(&cursor) <= gtk_text_iter_get_offset(&start)) {
|
||||
gtk_text_buffer_place_cursor(textBuffer, &start);
|
||||
} else {
|
||||
gtk_text_iter_set_offset(&cursor, gtk_text_iter_get_offset(&cursor) - 1);
|
||||
gtk_text_buffer_place_cursor(textBuffer, &cursor);
|
||||
}
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Right) {
|
||||
if(gtk_text_iter_get_offset(&cursor) < gtk_text_iter_get_offset(&start)) {
|
||||
gtk_text_buffer_place_cursor(textBuffer, &end);
|
||||
} else if(gtk_text_iter_get_offset(&cursor) < gtk_text_iter_get_offset(&end)) {
|
||||
gtk_text_iter_set_offset(&cursor, gtk_text_iter_get_offset(&cursor) + 1);
|
||||
gtk_text_buffer_place_cursor(textBuffer, &cursor);
|
||||
}
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Home) {
|
||||
gtk_text_buffer_place_cursor(textBuffer, &start);
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_End) {
|
||||
gtk_text_buffer_place_cursor(textBuffer, &end);
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_BackSpace) {
|
||||
if(gtk_text_iter_get_offset(&cursor) <= gtk_text_iter_get_offset(&start)) return true;
|
||||
GtkTextIter lhs = cursor;
|
||||
gtk_text_iter_set_offset(&lhs, gtk_text_iter_get_offset(&cursor) - 1);
|
||||
gtk_text_buffer_delete(textBuffer, &lhs, &cursor);
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_KEY_Delete) {
|
||||
if(gtk_text_iter_get_offset(&cursor) < gtk_text_iter_get_offset(&start)) return true;
|
||||
if(gtk_text_iter_get_offset(&cursor) == gtk_text_iter_get_offset(&end)) return true;
|
||||
GtkTextIter rhs = cursor;
|
||||
gtk_text_iter_set_offset(&rhs, gtk_text_iter_get_offset(&cursor) + 1);
|
||||
gtk_text_buffer_delete(textBuffer, &cursor, &rhs);
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode >= 0x20 && scancode <= 0x7e) {
|
||||
if(gtk_text_iter_get_offset(&cursor) < gtk_text_iter_get_offset(&start)) return true;
|
||||
gtk_text_buffer_insert(textBuffer, &cursor, string{(char)scancode}, -1);
|
||||
_seekToMark();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pConsole::_seekToEnd() -> void {
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &iter);
|
||||
gtk_text_buffer_place_cursor(textBuffer, &iter);
|
||||
_seekToMark();
|
||||
}
|
||||
|
||||
auto pConsole::_seekToMark() -> void {
|
||||
GtkTextMark* mark = gtk_text_buffer_get_mark(textBuffer, "insert");
|
||||
gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(subWidget), mark);
|
||||
}
|
||||
|
||||
}
|
23
hiro/gtk/widget/console.hpp
Normal file
23
hiro/gtk/widget/console.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pConsole : pWidget {
|
||||
Declare(Console, Widget)
|
||||
|
||||
auto print(const string& text) -> void;
|
||||
auto reset() -> void;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setPrompt(const string& prompt) -> void;
|
||||
|
||||
auto _keyPress(unsigned scancode, unsigned mask) -> bool;
|
||||
auto _seekToEnd() -> void;
|
||||
auto _seekToMark() -> void;
|
||||
|
||||
GtkWidget* subWidget = nullptr;
|
||||
GtkTextBuffer* textBuffer = nullptr;
|
||||
string previousPrompt;
|
||||
lstring history;
|
||||
unsigned historyOffset = 0;
|
||||
};
|
||||
|
||||
}
|
66
hiro/gtk/widget/frame.cpp
Normal file
66
hiro/gtk/widget/frame.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pFrame::construct() -> void {
|
||||
gtkWidget = gtk_frame_new(nullptr);
|
||||
gtkLabel = gtk_label_new("");
|
||||
gtk_frame_set_label_widget(GTK_FRAME(gtkWidget), gtkLabel);
|
||||
gtk_widget_show(gtkLabel);
|
||||
|
||||
setText(state().text);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pFrame::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pFrame::append(shared_pointer<mLayout> layout) -> void {
|
||||
if(auto layout = _layout()) layout->setFont(layout->self().font(true));
|
||||
}
|
||||
|
||||
auto pFrame::container(mWidget& widget) -> GtkWidget* {
|
||||
return gtk_widget_get_parent(gtkWidget);
|
||||
}
|
||||
|
||||
auto pFrame::remove(shared_pointer<mLayout> layout) -> void {
|
||||
}
|
||||
|
||||
auto pFrame::setEnabled(bool enabled) -> void {
|
||||
if(auto layout = _layout()) layout->setEnabled(layout->self().enabled(true));
|
||||
pWidget::setEnabled(enabled);
|
||||
}
|
||||
|
||||
auto pFrame::setFont(const string& font) -> void {
|
||||
if(auto layout = _layout()) layout->setFont(layout->self().font(true));
|
||||
pFont::setFont(gtkLabel, font);
|
||||
}
|
||||
|
||||
auto pFrame::setGeometry(Geometry geometry) -> void {
|
||||
pWidget::setGeometry(geometry);
|
||||
if(auto layout = state().layout) {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
if(!state().text) size.setHeight(10);
|
||||
geometry.setX(geometry.x() + 2);
|
||||
geometry.setY(geometry.y() + (size.height() - 1));
|
||||
geometry.setWidth(geometry.width() - 5);
|
||||
geometry.setHeight(geometry.height() - (size.height() + 2));
|
||||
layout->setGeometry(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
auto pFrame::setText(const string& text) -> void {
|
||||
gtk_label_set_text(GTK_LABEL(gtkLabel), text);
|
||||
}
|
||||
|
||||
auto pFrame::setVisible(bool visible) -> void {
|
||||
if(auto layout = _layout()) layout->setVisible(layout->self().visible(true));
|
||||
pWidget::setVisible(visible);
|
||||
}
|
||||
|
||||
auto pFrame::_layout() -> pLayout* {
|
||||
if(auto layout = state().layout) return layout->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
20
hiro/gtk/widget/frame.hpp
Normal file
20
hiro/gtk/widget/frame.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pFrame : pWidget {
|
||||
Declare(Frame, Widget)
|
||||
|
||||
auto append(shared_pointer<mLayout> layout) -> void;
|
||||
auto container(mWidget& widget) -> GtkWidget* override;
|
||||
auto remove(shared_pointer<mLayout> layout) -> void;
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setGeometry(Geometry geometry) -> void override;
|
||||
auto setText(const string& text) -> void;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto _layout() -> pLayout*;
|
||||
|
||||
GtkWidget* gtkLabel = nullptr;
|
||||
};
|
||||
|
||||
}
|
309
hiro/gtk/widget/hex-edit.cpp
Normal file
309
hiro/gtk/widget/hex-edit.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto HexEdit_keyPress(GtkWidget* widget, GdkEventKey* event, pHexEdit* p) -> signed {
|
||||
return p->keyPress(event->keyval, event->state);
|
||||
}
|
||||
|
||||
static auto HexEdit_mouseScroll(GtkWidget* widget, GdkEventScroll* event, pHexEdit* p) -> signed {
|
||||
double position = gtk_range_get_value(GTK_RANGE(p->scrollBar));
|
||||
|
||||
if(event->direction == GDK_SCROLL_UP) {
|
||||
p->scroll(position - 1);
|
||||
}
|
||||
|
||||
if(event->direction == GDK_SCROLL_DOWN) {
|
||||
p->scroll(position + 1);
|
||||
}
|
||||
|
||||
return true; //do not propagate event further
|
||||
}
|
||||
|
||||
static auto HexEdit_scroll(GtkRange* range, GtkScrollType scroll, double value, pHexEdit* p) -> signed {
|
||||
p->scroll((signed)value);
|
||||
return true; //do not propagate event further
|
||||
}
|
||||
|
||||
auto pHexEdit::construct() -> void {
|
||||
gtkWidget = gtk_hbox_new(false, 0);
|
||||
|
||||
container = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(container), GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
subWidget = gtk_text_view_new();
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), false);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_NONE);
|
||||
gtk_container_add(GTK_CONTAINER(container), subWidget);
|
||||
|
||||
scrollBar = gtk_vscrollbar_new((GtkAdjustment*)nullptr);
|
||||
gtk_range_set_range(GTK_RANGE(scrollBar), 0, 255);
|
||||
gtk_range_set_increments(GTK_RANGE(scrollBar), 1, 16);
|
||||
gtk_widget_set_sensitive(scrollBar, false);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(gtkWidget), container, true, true, 0);
|
||||
gtk_box_pack_start(GTK_BOX(gtkWidget), scrollBar, false, false, 1);
|
||||
|
||||
textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget));
|
||||
textCursor = gtk_text_buffer_get_mark(textBuffer, "insert");
|
||||
|
||||
gtk_widget_show(scrollBar);
|
||||
gtk_widget_show(subWidget);
|
||||
gtk_widget_show(container);
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setColumns(state().columns);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setRows(state().rows);
|
||||
setLength(state().length);
|
||||
setOffset(state().offset);
|
||||
update();
|
||||
|
||||
g_signal_connect(G_OBJECT(subWidget), "key-press-event", G_CALLBACK(HexEdit_keyPress), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(subWidget), "scroll-event", G_CALLBACK(HexEdit_mouseScroll), (gpointer)this);
|
||||
|
||||
g_signal_connect(G_OBJECT(scrollBar), "change-value", G_CALLBACK(HexEdit_scroll), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(scrollBar), "scroll-event", G_CALLBACK(HexEdit_mouseScroll), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pHexEdit::destruct() -> void {
|
||||
gtk_widget_destroy(scrollBar);
|
||||
gtk_widget_destroy(subWidget);
|
||||
gtk_widget_destroy(container);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pHexEdit::focused() const -> bool {
|
||||
return GTK_WIDGET_HAS_FOCUS(subWidget) || GTK_WIDGET_HAS_FOCUS(scrollBar);
|
||||
}
|
||||
|
||||
auto pHexEdit::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pHexEdit::setColumns(unsigned columns) -> void {
|
||||
setScroll();
|
||||
update();
|
||||
}
|
||||
|
||||
auto pHexEdit::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pHexEdit::setLength(unsigned length) -> void {
|
||||
setScroll();
|
||||
update();
|
||||
}
|
||||
|
||||
auto pHexEdit::setOffset(unsigned offset) -> void {
|
||||
setScroll();
|
||||
updateScroll();
|
||||
update();
|
||||
}
|
||||
|
||||
auto pHexEdit::setRows(unsigned rows) -> void {
|
||||
setScroll();
|
||||
update();
|
||||
}
|
||||
|
||||
auto pHexEdit::update() -> void {
|
||||
if(!state().onRead) {
|
||||
gtk_text_buffer_set_text(textBuffer, "", -1);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned position = cursorPosition();
|
||||
|
||||
string output;
|
||||
unsigned offset = state().offset;
|
||||
for(auto row : range(state().rows)) {
|
||||
output.append(hex<8>(offset));
|
||||
output.append(" ");
|
||||
|
||||
string hexdata;
|
||||
string ansidata = " ";
|
||||
for(auto column : range(state().columns)) {
|
||||
if(offset < state().length) {
|
||||
uint8_t data = self().doRead(offset++);
|
||||
hexdata.append(hex<2>(data));
|
||||
hexdata.append(" ");
|
||||
ansidata.append(data >= 0x20 && data <= 0x7e ? (char)data : '.');
|
||||
} else {
|
||||
hexdata.append(" ");
|
||||
ansidata.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
output.append(hexdata);
|
||||
output.append(ansidata);
|
||||
if(offset >= state().length) break;
|
||||
if(row != state().rows - 1) output.append("\n");
|
||||
}
|
||||
|
||||
gtk_text_buffer_set_text(textBuffer, output, -1);
|
||||
if(position == 0) position = 10; //start at first position where hex values can be entered
|
||||
setCursorPosition(position);
|
||||
}
|
||||
|
||||
auto pHexEdit::cursorPosition() -> unsigned {
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor);
|
||||
return gtk_text_iter_get_offset(&iter);
|
||||
}
|
||||
|
||||
auto pHexEdit::keyPress(unsigned scancode, unsigned mask) -> bool {
|
||||
if(!state().onRead) return false;
|
||||
|
||||
if(mask & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK)) return false; //allow actions such as Ctrl+C (copy)
|
||||
|
||||
signed position = cursorPosition();
|
||||
signed lineWidth = 10 + (state().columns * 3) + 1 + state().columns + 1;
|
||||
signed cursorY = position / lineWidth;
|
||||
signed cursorX = position % lineWidth;
|
||||
|
||||
if(scancode == GDK_Home) {
|
||||
setCursorPosition(cursorY * lineWidth + 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_End) {
|
||||
setCursorPosition(cursorY * lineWidth + 10 + (state().columns * 3 - 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_Up) {
|
||||
if(cursorY != 0) return false;
|
||||
|
||||
signed newOffset = state().offset - state().columns;
|
||||
if(newOffset >= 0) {
|
||||
self().setOffset(newOffset);
|
||||
update();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_Down) {
|
||||
if(cursorY >= rows() - 1) return true;
|
||||
if(cursorY != state().rows - 1) return false;
|
||||
|
||||
signed newOffset = state().offset + state().columns;
|
||||
if(newOffset + state().columns * state().rows - (state().columns - 1) <= state().length) {
|
||||
self().setOffset(newOffset);
|
||||
update();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_Page_Up) {
|
||||
signed newOffset = state().offset - state().columns * state().rows;
|
||||
if(newOffset >= 0) {
|
||||
self().setOffset(newOffset);
|
||||
} else {
|
||||
self().setOffset(0);
|
||||
}
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(scancode == GDK_Page_Down) {
|
||||
signed newOffset = state().offset + state().columns * state().rows;
|
||||
for(auto n : range(state().rows)) {
|
||||
if(newOffset + state().columns * state().rows - (state().columns - 1) <= state().length) {
|
||||
self().setOffset(newOffset);
|
||||
update();
|
||||
break;
|
||||
}
|
||||
newOffset -= state().columns;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//convert scancode to hex nibble
|
||||
if(scancode >= '0' && scancode <= '9') scancode = scancode - '0';
|
||||
else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10;
|
||||
else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10;
|
||||
else return false; //not a valid hex value
|
||||
|
||||
if(cursorX >= 10) {
|
||||
//not on an offset
|
||||
cursorX -= 10;
|
||||
if((cursorX % 3) != 2) {
|
||||
//not on a space
|
||||
bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low
|
||||
cursorX /= 3;
|
||||
if(cursorX < state().columns) {
|
||||
//not in ANSI region
|
||||
unsigned offset = state().offset + (cursorY * state().columns + cursorX);
|
||||
|
||||
if(offset >= state().length) return false; //do not edit past end of data
|
||||
uint8_t data = self().doRead(offset);
|
||||
|
||||
//write modified value
|
||||
if(cursorNibble == 1) {
|
||||
data = (data & 0xf0) | (scancode << 0);
|
||||
} else {
|
||||
data = (data & 0x0f) | (scancode << 4);
|
||||
}
|
||||
self().doWrite(offset, data);
|
||||
|
||||
//auto-advance cursor to next nibble/byte
|
||||
position++;
|
||||
if(cursorNibble && cursorX != state().columns - 1) position++;
|
||||
setCursorPosition(position);
|
||||
|
||||
//refresh output to reflect modified data
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//number of actual rows
|
||||
auto pHexEdit::rows() -> signed {
|
||||
return (max(1u, state().length) + state().columns - 1) / state().columns;
|
||||
}
|
||||
|
||||
//number of scrollable row positions
|
||||
auto pHexEdit::rowsScrollable() -> signed {
|
||||
return max(0u, rows() - state().rows);
|
||||
}
|
||||
|
||||
auto pHexEdit::scroll(signed position) -> void {
|
||||
if(position > rowsScrollable()) position = rowsScrollable();
|
||||
if(position < 0) position = 0;
|
||||
self().setOffset(position * state().columns);
|
||||
}
|
||||
|
||||
auto pHexEdit::setCursorPosition(unsigned position) -> void {
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor);
|
||||
|
||||
//GTK+ will throw many errors to the terminal if you set iterator past end of buffer
|
||||
GtkTextIter endIter;
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &iter);
|
||||
unsigned endPosition = gtk_text_iter_get_offset(&iter);
|
||||
|
||||
gtk_text_iter_set_offset(&iter, min(position, endPosition));
|
||||
gtk_text_buffer_place_cursor(textBuffer, &iter);
|
||||
}
|
||||
|
||||
auto pHexEdit::setScroll() -> void {
|
||||
if(rowsScrollable() > 0) {
|
||||
gtk_range_set_range(GTK_RANGE(scrollBar), 0, rowsScrollable());
|
||||
gtk_widget_set_sensitive(scrollBar, true);
|
||||
} else {
|
||||
gtk_widget_set_sensitive(scrollBar, false);
|
||||
}
|
||||
}
|
||||
|
||||
auto pHexEdit::updateScroll() -> void {
|
||||
unsigned row = state().offset / state().columns;
|
||||
gtk_range_set_value(GTK_RANGE(scrollBar), row);
|
||||
}
|
||||
|
||||
}
|
31
hiro/gtk/widget/hex-edit.hpp
Normal file
31
hiro/gtk/widget/hex-edit.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pHexEdit : pWidget {
|
||||
Declare(HexEdit, Widget)
|
||||
|
||||
auto focused() const -> bool override;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setColumns(unsigned columns) -> void;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setLength(unsigned length) -> void;
|
||||
auto setOffset(unsigned offset) -> void;
|
||||
auto setRows(unsigned rows) -> void;
|
||||
auto update() -> void;
|
||||
|
||||
auto cursorPosition() -> unsigned;
|
||||
auto keyPress(unsigned scancode, unsigned mask) -> bool;
|
||||
auto rows() -> signed;
|
||||
auto rowsScrollable() -> signed;
|
||||
auto scroll(signed position) -> void;
|
||||
auto setCursorPosition(unsigned position) -> void;
|
||||
auto setScroll() -> void;
|
||||
auto updateScroll() -> void;
|
||||
|
||||
GtkWidget* container = nullptr;
|
||||
GtkWidget* subWidget = nullptr;
|
||||
GtkWidget* scrollBar = nullptr;
|
||||
GtkTextBuffer* textBuffer = nullptr;
|
||||
GtkTextMark* textCursor = nullptr;
|
||||
};
|
||||
|
||||
}
|
41
hiro/gtk/widget/horizontal-scroller.cpp
Normal file
41
hiro/gtk/widget/horizontal-scroller.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto HorizontalScroller_change(GtkRange* gtkRange, pHorizontalScroller* p) -> void {
|
||||
auto position = (unsigned)gtk_range_get_value(gtkRange);
|
||||
if(p->state().position == position) return;
|
||||
p->state().position = position;
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
auto pHorizontalScroller::construct() -> void {
|
||||
gtkWidget = gtk_hscrollbar_new(0);
|
||||
|
||||
setLength(state().length);
|
||||
setPosition(state().position);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalScroller_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pHorizontalScroller::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pHorizontalScroller::minimumSize() const -> Size {
|
||||
return {0, 20};
|
||||
}
|
||||
|
||||
auto pHorizontalScroller::setLength(unsigned length) -> void {
|
||||
lock();
|
||||
length += length == 0;
|
||||
gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1));
|
||||
gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pHorizontalScroller::setPosition(unsigned position) -> void {
|
||||
gtk_range_set_value(GTK_RANGE(gtkWidget), position);
|
||||
}
|
||||
|
||||
}
|
11
hiro/gtk/widget/horizontal-scroller.hpp
Normal file
11
hiro/gtk/widget/horizontal-scroller.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pHorizontalScroller : pWidget {
|
||||
Declare(HorizontalScroller, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setLength(unsigned length) -> void;
|
||||
auto setPosition(unsigned position) -> void;
|
||||
};
|
||||
|
||||
}
|
40
hiro/gtk/widget/horizontal-slider.cpp
Normal file
40
hiro/gtk/widget/horizontal-slider.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto HorizontalSlider_change(GtkRange* gtkRange, pHorizontalSlider* p) -> void {
|
||||
auto position = (unsigned)gtk_range_get_value(gtkRange);
|
||||
if(p->state().position == position) return;
|
||||
p->state().position = position;
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
auto pHorizontalSlider::construct() -> void {
|
||||
gtkWidget = gtk_hscale_new_with_range(0, 100, 1);
|
||||
gtk_scale_set_draw_value(GTK_SCALE(gtkWidget), false);
|
||||
|
||||
setLength(state().length);
|
||||
setPosition(state().position);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalSlider_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pHorizontalSlider::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pHorizontalSlider::minimumSize() const -> Size {
|
||||
return {0, 20};
|
||||
}
|
||||
|
||||
auto pHorizontalSlider::setLength(unsigned length) -> void {
|
||||
length += length == 0;
|
||||
gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1));
|
||||
gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3);
|
||||
}
|
||||
|
||||
auto pHorizontalSlider::setPosition(unsigned position) -> void {
|
||||
gtk_range_set_value(GTK_RANGE(gtkWidget), position);
|
||||
}
|
||||
|
||||
}
|
11
hiro/gtk/widget/horizontal-slider.hpp
Normal file
11
hiro/gtk/widget/horizontal-slider.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pHorizontalSlider : pWidget {
|
||||
Declare(HorizontalSlider, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setLength(unsigned length) -> void;
|
||||
auto setPosition(unsigned position) -> void;
|
||||
};
|
||||
|
||||
}
|
32
hiro/gtk/widget/icon-view-item.cpp
Normal file
32
hiro/gtk/widget/icon-view-item.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pIconViewItem::construct() -> void {
|
||||
}
|
||||
|
||||
auto pIconViewItem::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pIconViewItem::setIcon(const image& icon) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemIcon(self().offset(), icon);
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconViewItem::setSelected(bool selected) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemSelected(self().offset(), selected);
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconViewItem::setText(const string& text) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemText(self().offset(), text);
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconViewItem::_parent() -> pIconView* {
|
||||
if(auto parent = self().parentIconView()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
13
hiro/gtk/widget/icon-view-item.hpp
Normal file
13
hiro/gtk/widget/icon-view-item.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pIconViewItem : pObject {
|
||||
Declare(IconViewItem, Object)
|
||||
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setSelected(bool selected) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _parent() -> pIconView*;
|
||||
};
|
||||
|
||||
}
|
224
hiro/gtk/widget/icon-view.cpp
Normal file
224
hiro/gtk/widget/icon-view.cpp
Normal file
@@ -0,0 +1,224 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto IconView_activate(GtkIconView* iconView, GtkTreePath* path, pIconView* p) -> void {
|
||||
if(!p->locked()) p->self().doActivate();
|
||||
}
|
||||
|
||||
static auto IconView_buttonEvent(GtkTreeView* treeView, GdkEventButton* event, pIconView* p) -> signed {
|
||||
if(event->type == GDK_BUTTON_RELEASE && event->button == 3) {
|
||||
if(!p->locked()) p->self().doContext();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto IconView_change(GtkIconView* iconView, pIconView* p) -> void {
|
||||
p->_updateSelected();
|
||||
}
|
||||
|
||||
auto pIconView::construct() -> void {
|
||||
gtkWidget = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
store = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
|
||||
subWidget = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
|
||||
gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_icon_view_set_text_column(GTK_ICON_VIEW(subWidget), 1);
|
||||
gtk_icon_view_set_reorderable(GTK_ICON_VIEW(subWidget), false);
|
||||
gtk_icon_view_set_margin(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_icon_view_set_spacing(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_icon_view_set_column_spacing(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_icon_view_set_row_spacing(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_icon_view_set_item_padding(GTK_ICON_VIEW(subWidget), 0);
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget);
|
||||
|
||||
gtk_widget_show(subWidget);
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setFlow(state().flow);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setMultiSelect(state().multiSelect);
|
||||
setOrientation(state().orientation);
|
||||
for(auto position : range(self().items())) {
|
||||
auto& item = state().items[position];
|
||||
append(item);
|
||||
}
|
||||
_updateSelected();
|
||||
|
||||
g_signal_connect(G_OBJECT(subWidget), "button-press-event", G_CALLBACK(IconView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(subWidget), "button-release-event", G_CALLBACK(IconView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(subWidget), "item-activated", G_CALLBACK(IconView_activate), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(subWidget), "selection-changed", G_CALLBACK(IconView_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pIconView::destruct() -> void {
|
||||
gtk_widget_destroy(subWidget);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pIconView::append(sIconViewItem item) -> void {
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(store, &iter);
|
||||
setItemIcon(item->offset(), item->state.icon);
|
||||
setItemSelected(item->offset(), item->state.selected);
|
||||
setItemText(item->offset(), item->state.text);
|
||||
}
|
||||
|
||||
auto pIconView::remove(sIconViewItem item) -> void {
|
||||
lock();
|
||||
GtkTreeIter iter;
|
||||
if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, string{item->offset()})) {
|
||||
gtk_list_store_remove(store, &iter);
|
||||
}
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::reset() -> void {
|
||||
lock();
|
||||
gtk_list_store_clear(store);
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pIconView::setFlow(Orientation flow) -> void {
|
||||
//GTK+ does not support vertical flow ... the closest we can get is a horizontal flow with only one column
|
||||
if(flow == Orientation::Horizontal) {
|
||||
gtk_icon_view_set_columns(GTK_ICON_VIEW(subWidget), -1);
|
||||
gtk_icon_view_set_item_width(GTK_ICON_VIEW(subWidget), -1);
|
||||
} else {
|
||||
gtk_icon_view_set_columns(GTK_ICON_VIEW(subWidget), 1);
|
||||
gtk_icon_view_set_item_width(GTK_ICON_VIEW(subWidget), max(128, pSizable::state().geometry.width() - 64));
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconView::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pIconView::setGeometry(Geometry geometry) -> void {
|
||||
pWidget::setGeometry(geometry);
|
||||
if(state().flow == Orientation::Vertical) {
|
||||
gtk_icon_view_set_item_width(GTK_ICON_VIEW(subWidget), max(128, pSizable::state().geometry.width() - 64));
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconView::setItemIcon(unsigned position, const image& icon) -> void {
|
||||
if(position >= self().items()) return;
|
||||
GtkTreeIter iter;
|
||||
if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, string{position})) {
|
||||
if(icon) {
|
||||
GdkPixbuf* pixbuf = CreatePixbuf(icon);
|
||||
gtk_list_store_set(store, &iter, 0, pixbuf, -1);
|
||||
} else {
|
||||
gtk_list_store_set(store, &iter, 0, nullptr, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconView::setItemSelected(unsigned position, bool selected) -> void {
|
||||
if(position >= self().items()) return;
|
||||
lock();
|
||||
GtkTreePath* path = gtk_tree_path_new_from_string(string{position});
|
||||
if(selected) {
|
||||
gtk_icon_view_select_path(GTK_ICON_VIEW(subWidget), path);
|
||||
} else {
|
||||
gtk_icon_view_unselect_path(GTK_ICON_VIEW(subWidget), path);
|
||||
}
|
||||
gtk_tree_path_free(path);
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::setItemSelected(const vector<signed>& selections) -> void {
|
||||
lock();
|
||||
setItemSelectedNone();
|
||||
for(auto& position : selections) setItemSelected(position, true);
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::setItemSelectedAll() -> void {
|
||||
lock();
|
||||
gtk_icon_view_select_all(GTK_ICON_VIEW(subWidget));
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::setItemSelectedNone() -> void {
|
||||
lock();
|
||||
gtk_icon_view_unselect_all(GTK_ICON_VIEW(subWidget));
|
||||
_updateSelected();
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pIconView::setItemText(unsigned position, const string& text) -> void {
|
||||
if(position >= self().items()) return;
|
||||
GtkTreeIter iter;
|
||||
if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, string{position})) {
|
||||
gtk_list_store_set(store, &iter, 1, (const char*)text, -1);
|
||||
}
|
||||
}
|
||||
|
||||
auto pIconView::setMultiSelect(bool multiSelect) -> void {
|
||||
gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(subWidget),
|
||||
multiSelect ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE
|
||||
);
|
||||
}
|
||||
|
||||
auto pIconView::setOrientation(Orientation orientation) -> void {
|
||||
gtk_icon_view_set_item_orientation(GTK_ICON_VIEW(subWidget),
|
||||
orientation == Orientation::Horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL
|
||||
);
|
||||
}
|
||||
|
||||
auto pIconView::_updateSelected() -> void {
|
||||
vector<unsigned> selected;
|
||||
|
||||
GList* list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(subWidget));
|
||||
GList* p = list;
|
||||
|
||||
while(p) {
|
||||
auto path = (GtkTreePath*)p->data;
|
||||
char* pathString = gtk_tree_path_to_string(path);
|
||||
unsigned position = decimal(pathString);
|
||||
g_free(pathString);
|
||||
selected.append(position);
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
g_list_foreach(list, (GFunc)gtk_tree_path_free, nullptr);
|
||||
g_list_free(list);
|
||||
|
||||
bool identical = selected.size() == currentSelection.size();
|
||||
if(identical) {
|
||||
for(unsigned n = 0; n < selected.size(); n++) {
|
||||
if(selected[n] != currentSelection[n]) {
|
||||
identical = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(identical) return;
|
||||
|
||||
currentSelection = selected;
|
||||
for(auto& item : state().items) item->state.selected = false;
|
||||
for(auto& position : currentSelection) {
|
||||
if(position >= self().items()) continue;
|
||||
state().items[position]->state.selected = true;
|
||||
}
|
||||
|
||||
if(!locked()) self().doChange();
|
||||
}
|
||||
|
||||
}
|
29
hiro/gtk/widget/icon-view.hpp
Normal file
29
hiro/gtk/widget/icon-view.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pIconView : pWidget {
|
||||
Declare(IconView, Widget)
|
||||
|
||||
auto append(sIconViewItem item) -> void;
|
||||
auto remove(sIconViewItem item) -> void;
|
||||
auto reset() -> void;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setFlow(Orientation flow) -> void;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setGeometry(Geometry geometry) -> void;
|
||||
auto setItemIcon(unsigned position, const image& icon) -> void;
|
||||
auto setItemSelected(unsigned position, bool selected) -> void;
|
||||
auto setItemSelected(const vector<signed>& selections) -> void;
|
||||
auto setItemSelectedAll() -> void;
|
||||
auto setItemSelectedNone() -> void;
|
||||
auto setItemText(unsigned position, const string& text) -> void;
|
||||
auto setMultiSelect(bool multiSelect) -> void;
|
||||
auto setOrientation(Orientation orientation) -> void;
|
||||
|
||||
auto _updateSelected() -> void;
|
||||
|
||||
GtkWidget* subWidget = nullptr;
|
||||
GtkListStore* store = nullptr;
|
||||
vector<unsigned> currentSelection;
|
||||
};
|
||||
|
||||
}
|
41
hiro/gtk/widget/label.cpp
Normal file
41
hiro/gtk/widget/label.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pLabel::construct() -> void {
|
||||
gtkWidget = gtk_label_new("");
|
||||
|
||||
_setAlignment();
|
||||
setText(state().text);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pLabel::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pLabel::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
return {size.width(), size.height()};
|
||||
}
|
||||
|
||||
auto pLabel::setHorizontalAlignment(double alignment) -> void {
|
||||
_setAlignment();
|
||||
}
|
||||
|
||||
auto pLabel::setText(const string& text) -> void {
|
||||
gtk_label_set_text(GTK_LABEL(gtkWidget), text);
|
||||
}
|
||||
|
||||
auto pLabel::setVerticalAlignment(double alignment) -> void {
|
||||
_setAlignment();
|
||||
}
|
||||
|
||||
auto pLabel::_setAlignment() -> void {
|
||||
gtk_misc_set_alignment(GTK_MISC(gtkWidget), state().horizontalAlignment, state().verticalAlignment);
|
||||
auto justify = GTK_JUSTIFY_CENTER;
|
||||
if(state().horizontalAlignment < 0.333) justify = GTK_JUSTIFY_LEFT;
|
||||
if(state().horizontalAlignment > 0.666) justify = GTK_JUSTIFY_RIGHT;
|
||||
gtk_label_set_justify(GTK_LABEL(gtkWidget), justify);
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/widget/label.hpp
Normal file
14
hiro/gtk/widget/label.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pLabel : pWidget {
|
||||
Declare(Label, Widget)
|
||||
|
||||
auto minimumSize() const -> Size override;
|
||||
auto setHorizontalAlignment(double alignment) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
auto setVerticalAlignment(double alignment) -> void;
|
||||
|
||||
auto _setAlignment() -> void;
|
||||
};
|
||||
|
||||
}
|
55
hiro/gtk/widget/line-edit.cpp
Normal file
55
hiro/gtk/widget/line-edit.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto LineEdit_activate(GtkEntry*, pLineEdit* p) -> void {
|
||||
p->self().doActivate();
|
||||
}
|
||||
|
||||
static auto LineEdit_change(GtkEditable*, pLineEdit* p) -> void {
|
||||
p->state().text = gtk_entry_get_text(GTK_ENTRY(p->gtkWidget));
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
auto pLineEdit::construct() -> void {
|
||||
gtkWidget = gtk_entry_new();
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setEditable(state().editable);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "activate", G_CALLBACK(LineEdit_activate), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "changed", G_CALLBACK(LineEdit_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pLineEdit::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pLineEdit::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
return {size.width() + 10, size.height() + 10};
|
||||
}
|
||||
|
||||
auto pLineEdit::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(gtkWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pLineEdit::setEditable(bool editable) -> void {
|
||||
gtk_editable_set_editable(GTK_EDITABLE(gtkWidget), editable);
|
||||
}
|
||||
|
||||
auto pLineEdit::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(gtkWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pLineEdit::setText(const string& text) -> void {
|
||||
lock();
|
||||
gtk_entry_set_text(GTK_ENTRY(gtkWidget), text);
|
||||
unlock();
|
||||
}
|
||||
|
||||
}
|
13
hiro/gtk/widget/line-edit.hpp
Normal file
13
hiro/gtk/widget/line-edit.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pLineEdit : pWidget {
|
||||
Declare(LineEdit, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setEditable(bool editable) -> void;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
};
|
||||
|
||||
}
|
133
hiro/gtk/widget/list-view-column.cpp
Normal file
133
hiro/gtk/widget/list-view-column.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pListViewColumn::construct() -> void {
|
||||
unsigned offset = self().offset();
|
||||
|
||||
gtkHeader = gtk_hbox_new(false, 0);
|
||||
|
||||
gtkHeaderIcon = gtk_image_new();
|
||||
gtk_box_pack_start(GTK_BOX(gtkHeader), gtkHeaderIcon, false, false, 0);
|
||||
|
||||
gtkHeaderText = gtk_label_new(state().text);
|
||||
gtk_box_pack_start(GTK_BOX(gtkHeader), gtkHeaderText, true, false, 2);
|
||||
|
||||
gtkColumn = gtk_tree_view_column_new();
|
||||
gtk_tree_view_column_set_sizing(gtkColumn, GTK_TREE_VIEW_COLUMN_FIXED);
|
||||
gtk_tree_view_column_set_title(gtkColumn, "");
|
||||
gtk_tree_view_column_set_widget(gtkColumn, gtkHeader);
|
||||
|
||||
if(offset == 0) {
|
||||
gtkCellToggle = gtk_cell_renderer_toggle_new();
|
||||
gtk_tree_view_column_pack_start(gtkColumn, gtkCellToggle, false);
|
||||
gtk_tree_view_column_set_attributes(gtkColumn, gtkCellToggle, "active", 0, nullptr);
|
||||
}
|
||||
|
||||
gtkCellIcon = gtk_cell_renderer_pixbuf_new();
|
||||
gtk_tree_view_column_pack_start(gtkColumn, gtkCellIcon, false);
|
||||
gtk_tree_view_column_set_attributes(gtkColumn, gtkCellIcon, "pixbuf", 1 + offset * 2 + 0, nullptr);
|
||||
|
||||
gtkCellText = gtk_cell_renderer_text_new();
|
||||
gtk_tree_view_column_pack_start(gtkColumn, gtkCellText, false);
|
||||
gtk_tree_view_column_set_attributes(gtkColumn, gtkCellText, "text", 1 + offset * 2 + 1, nullptr);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkColumn), "clicked", G_CALLBACK(ListView_headerActivate), (gpointer)_parent());
|
||||
g_signal_connect(G_OBJECT(gtkCellText), "edited", G_CALLBACK(ListView_edit), (gpointer)_parent());
|
||||
if(gtkCellToggle) g_signal_connect(G_OBJECT(gtkCellToggle), "toggled", G_CALLBACK(ListView_toggle), (gpointer)_parent());
|
||||
}
|
||||
|
||||
auto pListViewColumn::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pListViewColumn::setActive() -> void {
|
||||
if(auto parent = _parent()) {
|
||||
gtk_tree_view_set_search_column(parent->gtkTreeView, 1 + self().offset() * 2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewColumn::setBackgroundColor(Color color) -> void {
|
||||
if(color) {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
if(gtkCellToggle) g_object_set(G_OBJECT(gtkCellToggle), "cell-background-gdk", &gdkColor, nullptr);
|
||||
g_object_set(G_OBJECT(gtkCellIcon), "cell-background-gdk", &gdkColor, nullptr);
|
||||
g_object_set(G_OBJECT(gtkCellText), "cell-background-gdk", &gdkColor, nullptr);
|
||||
} else {
|
||||
if(gtkCellToggle) g_object_set(G_OBJECT(gtkCellToggle), "cell-background-set", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(gtkCellIcon), "cell-background-set", FALSE, nullptr);
|
||||
g_object_set(G_OBJECT(gtkCellText), "cell-background-set", FALSE, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewColumn::setEditable(bool editable) -> void {
|
||||
g_object_set(G_OBJECT(gtkCellText), "editable", editable ? TRUE : FALSE, nullptr);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setFont(const string& font) -> void {
|
||||
pFont::setFont(gtkHeaderText, font);
|
||||
auto fontDescription = pFont::create(font);
|
||||
g_object_set(G_OBJECT(gtkCellText), "font-desc", fontDescription, nullptr);
|
||||
pango_font_description_free(fontDescription);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setForegroundColor(Color color) -> void {
|
||||
if(color) {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
g_object_set(G_OBJECT(gtkCellText), "foreground-gdk", &gdkColor, nullptr);
|
||||
} else {
|
||||
g_object_set(G_OBJECT(gtkCellText), "foreground-set", FALSE, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewColumn::setHorizontalAlignment(double alignment) -> void {
|
||||
_setAlignment();
|
||||
}
|
||||
|
||||
auto pListViewColumn::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(gtkHeaderIcon), CreatePixbuf(icon));
|
||||
} else {
|
||||
gtk_image_clear(GTK_IMAGE(gtkHeaderIcon));
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewColumn::setResizable(bool resizable) -> void {
|
||||
gtk_tree_view_column_set_resizable(gtkColumn, resizable);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setSortable(bool sortable) -> void {
|
||||
gtk_tree_view_column_set_clickable(gtkColumn, sortable);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setText(const string& text) -> void {
|
||||
gtk_label_set_text(GTK_LABEL(gtkHeaderText), text);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setVerticalAlignment(double alignment) -> void {
|
||||
_setAlignment();
|
||||
}
|
||||
|
||||
auto pListViewColumn::setVisible(bool visible) -> void {
|
||||
gtk_tree_view_column_set_visible(gtkColumn, visible);
|
||||
}
|
||||
|
||||
auto pListViewColumn::setWidth(signed width) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->resizeColumns();
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewColumn::_parent() -> pListView* {
|
||||
if(auto parent = self().parentListView()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pListViewColumn::_setAlignment() -> void {
|
||||
gtk_tree_view_column_set_alignment(gtkColumn, state().horizontalAlignment);
|
||||
gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(gtkCellText), state().horizontalAlignment, state().verticalAlignment);
|
||||
//set multi-line text alignment
|
||||
auto pangoAlignment = PANGO_ALIGN_CENTER;
|
||||
if(state().horizontalAlignment < 0.333) pangoAlignment = PANGO_ALIGN_LEFT;
|
||||
if(state().horizontalAlignment > 0.666) pangoAlignment = PANGO_ALIGN_RIGHT;
|
||||
g_object_set(G_OBJECT(gtkCellText), "alignment", pangoAlignment, nullptr);
|
||||
}
|
||||
|
||||
}
|
32
hiro/gtk/widget/list-view-column.hpp
Normal file
32
hiro/gtk/widget/list-view-column.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pListViewColumn : pObject {
|
||||
Declare(ListViewColumn, Object)
|
||||
|
||||
auto setActive() -> void;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setEditable(bool editable) -> void;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setHorizontalAlignment(double alignment) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setResizable(bool resizable) -> void;
|
||||
auto setSortable(bool sortable) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
auto setVerticalAlignment(double alignment) -> void;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
auto setWidth(signed width) -> void;
|
||||
|
||||
auto _parent() -> pListView*;
|
||||
auto _setAlignment() -> void;
|
||||
|
||||
GtkTreeViewColumn* gtkColumn = nullptr;
|
||||
GtkWidget* gtkHeader = nullptr;
|
||||
GtkWidget* gtkHeaderIcon = nullptr;
|
||||
GtkWidget* gtkHeaderText = nullptr;
|
||||
GtkCellRenderer* gtkCellToggle = nullptr;
|
||||
GtkCellRenderer* gtkCellIcon = nullptr;
|
||||
GtkCellRenderer* gtkCellText = nullptr;
|
||||
};
|
||||
|
||||
}
|
59
hiro/gtk/widget/list-view-item.cpp
Normal file
59
hiro/gtk/widget/list-view-item.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pListViewItem::construct() -> void {
|
||||
}
|
||||
|
||||
auto pListViewItem::destruct() -> void {
|
||||
}
|
||||
|
||||
auto pListViewItem::setChecked(bool checked) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 0, checked, -1);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewItem::setFocused() -> void {
|
||||
if(auto parent = _parent()) {
|
||||
GtkTreePath* path = gtk_tree_path_new_from_string(string{self().offset()});
|
||||
gtk_tree_view_set_cursor(parent->gtkTreeView, path, nullptr, false);
|
||||
gtk_tree_view_scroll_to_cell(parent->gtkTreeView, path, nullptr, true, 0.5, 0.0);
|
||||
gtk_tree_path_free(path);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewItem::setIcon(unsigned column, const image& icon) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
if(icon) {
|
||||
auto pixbuf = CreatePixbuf(icon, true);
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2, pixbuf, -1);
|
||||
} else {
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2, nullptr, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewItem::setSelected(bool selected) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->lock();
|
||||
if(selected) {
|
||||
gtk_tree_selection_select_iter(parent->gtkTreeSelection, >kIter);
|
||||
} else {
|
||||
gtk_tree_selection_unselect_iter(parent->gtkTreeSelection, >kIter);
|
||||
}
|
||||
parent->_updateSelected();
|
||||
parent->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewItem::setText(unsigned column, const string& text) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
gtk_list_store_set(parent->gtkListStore, >kIter, 1 + column * 2 + 1, text.data(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListViewItem::_parent() -> pListView* {
|
||||
if(auto parent = self().parentListView()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
17
hiro/gtk/widget/list-view-item.hpp
Normal file
17
hiro/gtk/widget/list-view-item.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pListViewItem : pObject {
|
||||
Declare(ListViewItem, Object)
|
||||
|
||||
auto setChecked(bool checked) -> void;
|
||||
auto setFocused() -> void;
|
||||
auto setIcon(unsigned column, const image& icon) -> void;
|
||||
auto setSelected(bool selected) -> void;
|
||||
auto setText(unsigned column, const string& text) -> void;
|
||||
|
||||
auto _parent() -> pListView*;
|
||||
|
||||
GtkTreeIter gtkIter;
|
||||
};
|
||||
|
||||
}
|
369
hiro/gtk/widget/list-view.cpp
Normal file
369
hiro/gtk/widget/list-view.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto ListView_activate(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, pListView* p) -> void { return p->_doActivate(); }
|
||||
static auto ListView_buttonEvent(GtkTreeView* treeView, GdkEventButton* event, pListView* p) -> signed { return p->_doEvent(event); }
|
||||
static auto ListView_change(GtkTreeSelection*, pListView* p) -> void { return p->_doChange(); }
|
||||
static auto ListView_edit(GtkCellRendererText* renderer, const char* path, const char* text, pListView* p) -> void { return p->_doEdit(renderer, path, text); }
|
||||
static auto ListView_headerActivate(GtkTreeViewColumn* column, pListView* p) -> void { return p->_doHeaderActivate(column); }
|
||||
static auto ListView_mouseMoveEvent(GtkWidget*, GdkEvent*, pListView* p) -> signed { return p->_doMouseMove(); }
|
||||
static auto ListView_popup(GtkTreeView*, pListView* p) -> void { return p->_doContext(); }
|
||||
static auto ListView_toggle(GtkCellRendererToggle*, const char* path, pListView* p) -> void { return p->_doToggle(path); }
|
||||
|
||||
auto pListView::construct() -> void {
|
||||
gtkWidget = gtk_scrolled_window_new(0, 0);
|
||||
gtkScrolledWindow = GTK_SCROLLED_WINDOW(gtkWidget);
|
||||
gtk_scrolled_window_set_policy(gtkScrolledWindow, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type(gtkScrolledWindow, GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
gtkWidgetChild = gtk_tree_view_new();
|
||||
gtkTreeView = GTK_TREE_VIEW(gtkWidgetChild);
|
||||
gtkTreeSelection = gtk_tree_view_get_selection(gtkTreeView);
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), gtkWidgetChild);
|
||||
gtk_tree_view_set_rubber_banding(gtkTreeView, true);
|
||||
|
||||
gtk_widget_show(gtkWidgetChild);
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setCheckable(state().checkable);
|
||||
setFont(self().font(true));
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setGridVisible(state().gridVisible);
|
||||
setHeaderVisible(state().headerVisible);
|
||||
setMultiSelect(state().multiSelect);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "button-press-event", G_CALLBACK(ListView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "button-release-event", G_CALLBACK(ListView_buttonEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "motion-notify-event", G_CALLBACK(ListView_mouseMoveEvent), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "popup-menu", G_CALLBACK(ListView_popup), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeView), "row-activated", G_CALLBACK(ListView_activate), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkTreeSelection), "changed", G_CALLBACK(ListView_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pListView::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidgetChild);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pListView::append(sListViewColumn column) -> void {
|
||||
gtk_tree_view_append_column(gtkTreeView, column->self()->gtkColumn);
|
||||
gtk_widget_show_all(column->self()->gtkHeader);
|
||||
column->setFont(column->font());
|
||||
setCheckable(state().checkable);
|
||||
_createModel();
|
||||
gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column
|
||||
}
|
||||
|
||||
auto pListView::append(sListViewItem item) -> void {
|
||||
gtk_list_store_append(gtkListStore, &item->self()->gtkIter);
|
||||
|
||||
item->setChecked(item->checked());
|
||||
item->setSelected(item->selected());
|
||||
for(auto column : range(self().columns())) {
|
||||
item->setIcon(column, item->state.icon(column, {}));
|
||||
item->setText(column, item->state.text(column, ""));
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::focused() -> bool {
|
||||
return GTK_WIDGET_HAS_FOCUS(gtkTreeView);
|
||||
}
|
||||
|
||||
auto pListView::remove(sListViewColumn column) -> void {
|
||||
if(auto delegate = column->self()) {
|
||||
gtk_tree_view_remove_column(gtkTreeView, delegate->gtkColumn);
|
||||
delegate->gtkColumn = nullptr;
|
||||
}
|
||||
_createModel();
|
||||
gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column
|
||||
}
|
||||
|
||||
auto pListView::remove(sListViewItem item) -> void {
|
||||
lock();
|
||||
if(auto delegate = item->self()) {
|
||||
gtk_list_store_remove(gtkListStore, &delegate->gtkIter);
|
||||
_updateSelected();
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pListView::reset() -> void {
|
||||
GList* list = gtk_tree_view_get_columns(gtkTreeView), *p = list;
|
||||
while(p && p->data) {
|
||||
gtk_tree_view_remove_column(gtkTreeView, (GtkTreeViewColumn*)p->data);
|
||||
p = p->next;
|
||||
}
|
||||
g_list_free(list);
|
||||
_createModel();
|
||||
gtk_tree_view_set_rules_hint(gtkTreeView, false);
|
||||
}
|
||||
|
||||
//column widths:
|
||||
//< 0 = expanding (consume all remaining space)
|
||||
// 0 = auto (resize to contents
|
||||
//> 0 = fixed width
|
||||
auto pListView::resizeColumns() -> void {
|
||||
lock();
|
||||
|
||||
//compute the minimum width required for each column based upon the contents of all rows
|
||||
vector<signed> minimumWidths;
|
||||
for(auto column : range(self().columns())) {
|
||||
signed maximumWidth = 1;
|
||||
if(self().headerVisible()) {
|
||||
maximumWidth = max(maximumWidth, 8 //margin
|
||||
+ state().columns[column]->state.icon.width
|
||||
+ Font::size(state().columns[column]->font(true), state().columns[column]->state.text).width()
|
||||
);
|
||||
}
|
||||
for(auto row : range(self().items())) {
|
||||
maximumWidth = max(maximumWidth, 8 //margin
|
||||
+ (row == 0 && state().checkable ? 24 : 0) //check box
|
||||
+ state().items[row]->state.icon(column, {}).width
|
||||
+ Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width()
|
||||
);
|
||||
}
|
||||
if(!state().columns[column]->visible()) maximumWidth = 1;
|
||||
minimumWidths.append(maximumWidth);
|
||||
}
|
||||
|
||||
//subtract the widths of all non-expanding columns from the available widget space
|
||||
signed expansions = 0; //count the number of expanded columns
|
||||
signed emptyWidth = pSizable::state().geometry.width() - 5; //margin
|
||||
for(auto column : range(self().columns())) {
|
||||
signed width = state().columns[column]->width();
|
||||
if(!state().columns[column]->visible()) width = 1;
|
||||
if(width < 0) { expansions++; continue; }
|
||||
if(width == 0) width = minimumWidths[column];
|
||||
emptyWidth -= width;
|
||||
}
|
||||
|
||||
//the vertical scroll bar consumes header space when visible; subtract it from available space if needed
|
||||
auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow);
|
||||
if(scrollBar && gtk_widget_get_visible(scrollBar)) {
|
||||
emptyWidth -= scrollBar->allocation.width;
|
||||
}
|
||||
|
||||
//divide remaining space among all expanded columns
|
||||
if(expansions && emptyWidth >= expansions) emptyWidth /= expansions;
|
||||
else emptyWidth = 1;
|
||||
|
||||
for(auto column : range(self().columns())) {
|
||||
signed width = state().columns[column]->width();
|
||||
if(!state().columns[column]->visible()) width = 1;
|
||||
if(width < 0) width = emptyWidth;
|
||||
if(width == 0) width = minimumWidths[column];
|
||||
gtk_tree_view_column_set_fixed_width(_column(column)->gtkColumn, width);
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pListView::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pListView::setCheckable(bool checkable) -> void {
|
||||
if(auto delegate = _column(0)) {
|
||||
gtk_cell_renderer_set_visible(delegate->gtkCellToggle, checkable);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::setChecked(bool checked) -> void {
|
||||
for(auto& item : state().items) {
|
||||
if(auto delegate = item->self()) delegate->setChecked(checked);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::setFocused() -> void {
|
||||
gtk_widget_grab_focus(gtkWidgetChild);
|
||||
}
|
||||
|
||||
auto pListView::setFont(const string& font) -> void {
|
||||
for(auto& column : state().columns) {
|
||||
if(auto delegate = column->self()) delegate->setFont(column->font(true));
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pListView::setGridVisible(bool visible) -> void {
|
||||
gtk_tree_view_set_grid_lines(gtkTreeView, visible ? GTK_TREE_VIEW_GRID_LINES_BOTH : GTK_TREE_VIEW_GRID_LINES_NONE);
|
||||
}
|
||||
|
||||
auto pListView::setHeaderVisible(bool visible) -> void {
|
||||
gtk_tree_view_set_headers_visible(gtkTreeView, visible);
|
||||
}
|
||||
|
||||
auto pListView::setMultiSelect(bool multiSelect) -> void {
|
||||
gtk_tree_selection_set_mode(gtkTreeSelection, multiSelect ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);
|
||||
}
|
||||
|
||||
auto pListView::setSelected(bool selected) -> void {
|
||||
for(auto& item : state().items) {
|
||||
if(auto delegate = item->self()) delegate->setSelected(selected);
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::_column(unsigned column) -> pListViewColumn* {
|
||||
if(auto delegate = self().column(column)) return delegate->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pListView::_createModel() -> void {
|
||||
gtk_tree_view_set_model(gtkTreeView, nullptr);
|
||||
gtkListStore = nullptr;
|
||||
gtkTreeModel = nullptr;
|
||||
|
||||
vector<GType> types;
|
||||
unsigned position = 0;
|
||||
for(auto column : state().columns) {
|
||||
if(!column->self()->gtkColumn) continue; //column is being removed
|
||||
if(position++ == 0) types.append(G_TYPE_BOOLEAN);
|
||||
types.append(GDK_TYPE_PIXBUF);
|
||||
types.append(G_TYPE_STRING);
|
||||
}
|
||||
if(!types) return; //no columns available
|
||||
|
||||
gtkListStore = gtk_list_store_newv(types.size(), types.data());
|
||||
gtkTreeModel = GTK_TREE_MODEL(gtkListStore);
|
||||
gtk_tree_view_set_model(gtkTreeView, gtkTreeModel);
|
||||
}
|
||||
|
||||
auto pListView::_doActivate() -> void {
|
||||
if(!locked()) self().doActivate();
|
||||
}
|
||||
|
||||
auto pListView::_doChange() -> void {
|
||||
if(!locked()) _updateSelected();
|
||||
}
|
||||
|
||||
auto pListView::_doContext() -> void {
|
||||
if(!locked()) self().doContext();
|
||||
}
|
||||
|
||||
auto pListView::_doEdit(GtkCellRendererText* gtkCellRendererText, const char* path, const char* text) -> void {
|
||||
for(auto& column : state().columns) {
|
||||
if(auto delegate = column->self()) {
|
||||
if(gtkCellRendererText = GTK_CELL_RENDERER_TEXT(delegate->gtkCellText)) {
|
||||
if(auto item = self().item(decimal(path))) {
|
||||
if(string{text} != item->text(column->offset())) {
|
||||
item->setText(column->offset(), text);
|
||||
if(!locked()) self().doEdit(item, column);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pListView::_doEvent(GdkEventButton* event) -> signed {
|
||||
GtkTreePath* path = nullptr;
|
||||
gtk_tree_view_get_path_at_pos(gtkTreeView, event->x, event->y, &path, nullptr, nullptr, nullptr);
|
||||
|
||||
if(event->type == GDK_BUTTON_PRESS) {
|
||||
//when clicking in empty space below the last list view item; GTK+ does not deselect all items;
|
||||
//below code enables this functionality, to match behavior with all other UI toolkits (and because it's very convenient to have)
|
||||
if(path == nullptr && gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) {
|
||||
self().setSelected({});
|
||||
self().doChange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->type == GDK_BUTTON_PRESS && event->button == 3) {
|
||||
//this check prevents the loss of selection on other items if the item under the mouse cursor is currently selected
|
||||
if(path && gtk_tree_selection_path_is_selected(gtkTreeSelection, path)) return true;
|
||||
}
|
||||
|
||||
if(event->type == GDK_BUTTON_RELEASE && event->button == 3) {
|
||||
//handle action during right-click release; as button-press-event is sent prior to selection update
|
||||
//without this, the callback handler would see the previous selection state instead
|
||||
self().doContext();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pListView::_doHeaderActivate(GtkTreeViewColumn* gtkTreeViewColumn) -> void {
|
||||
for(auto& column : state().columns) {
|
||||
if(auto delegate = column->self()) {
|
||||
if(gtkTreeViewColumn == delegate->gtkColumn) {
|
||||
if(!locked()) self().doSort(column);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//GtkTreeView::cursor-changed and GtkTreeSelection::changed do not send signals for changes during rubber-banding selection
|
||||
//so here we capture motion-notify-event, and if the selections have changed, invoke ListView::onChange
|
||||
auto pListView::_doMouseMove() -> signed {
|
||||
if(gtk_tree_view_is_rubber_banding_active(gtkTreeView)) {
|
||||
if(!locked()) _updateSelected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pListView::_doToggle(const char* path) -> void {
|
||||
if(auto item = self().item(decimal(path))) {
|
||||
if(auto delegate = item->self()) {
|
||||
item->state.checked = !item->state.checked;
|
||||
delegate->setChecked(item->state.checked);
|
||||
if(!locked()) self().doToggle(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//compare currently selected items to previously selected items
|
||||
//if different, invoke the onChange callback unless locked, and cache current selection
|
||||
//this prevents firing an onChange event when the actual selection has not changed
|
||||
//this is particularly important for the motion-notify-event binding
|
||||
auto pListView::_updateSelected() -> void {
|
||||
vector<unsigned> selected;
|
||||
|
||||
GList* list = gtk_tree_selection_get_selected_rows(gtkTreeSelection, >kTreeModel);
|
||||
GList* p = list;
|
||||
|
||||
while(p) {
|
||||
GtkTreeIter iter;
|
||||
if(gtk_tree_model_get_iter(gtkTreeModel, &iter, (GtkTreePath*)p->data)) {
|
||||
char* pathname = gtk_tree_model_get_string_from_iter(gtkTreeModel, &iter);
|
||||
unsigned selection = decimal(pathname);
|
||||
g_free(pathname);
|
||||
selected.append(selection);
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
g_list_foreach(list, (GFunc)gtk_tree_path_free, nullptr);
|
||||
g_list_free(list);
|
||||
|
||||
bool identical = selected.size() == currentSelection.size();
|
||||
if(identical) {
|
||||
for(auto n : range(selected)) {
|
||||
if(selected[n] != currentSelection[n]) {
|
||||
identical = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(identical) return;
|
||||
|
||||
currentSelection = selected;
|
||||
for(auto& item : state().items) item->state.selected = false;
|
||||
for(auto& position : currentSelection) {
|
||||
if(position >= self().items()) continue;
|
||||
self().item(position)->state.selected = true;
|
||||
}
|
||||
|
||||
if(!locked()) self().doChange();
|
||||
}
|
||||
|
||||
}
|
45
hiro/gtk/widget/list-view.hpp
Normal file
45
hiro/gtk/widget/list-view.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pListView : pWidget {
|
||||
Declare(ListView, Widget)
|
||||
|
||||
auto append(sListViewColumn column) -> void;
|
||||
auto append(sListViewItem item) -> void;
|
||||
auto focused() -> bool;
|
||||
auto remove(sListViewColumn column) -> void;
|
||||
auto remove(sListViewItem item) -> void;
|
||||
auto reset() -> void;
|
||||
auto resizeColumns() -> void;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setCheckable(bool checkable) -> void;
|
||||
auto setChecked(bool checked) -> void;
|
||||
auto setFocused() -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setGridVisible(bool visible) -> void;
|
||||
auto setHeaderVisible(bool visible) -> void;
|
||||
auto setMultiSelect(bool multiSelect) -> void;
|
||||
auto setSelected(bool selected) -> void;
|
||||
|
||||
auto _column(unsigned column) -> pListViewColumn*;
|
||||
auto _createModel() -> void;
|
||||
auto _doActivate() -> void;
|
||||
auto _doChange() -> void;
|
||||
auto _doContext() -> void;
|
||||
auto _doEdit(GtkCellRendererText* renderer, const char* path, const char* text) -> void;
|
||||
auto _doEvent(GdkEventButton* event) -> signed;
|
||||
auto _doHeaderActivate(GtkTreeViewColumn* column) -> void;
|
||||
auto _doMouseMove() -> signed;
|
||||
auto _doToggle(const char* path) -> void;
|
||||
auto _updateSelected() -> void;
|
||||
|
||||
GtkScrolledWindow* gtkScrolledWindow = nullptr;
|
||||
GtkWidget* gtkWidgetChild = nullptr;
|
||||
GtkTreeView* gtkTreeView = nullptr;
|
||||
GtkTreeSelection* gtkTreeSelection = nullptr;
|
||||
GtkListStore* gtkListStore = nullptr;
|
||||
GtkTreeModel* gtkTreeModel = nullptr;
|
||||
vector<unsigned> currentSelection;
|
||||
};
|
||||
|
||||
}
|
24
hiro/gtk/widget/progress-bar.cpp
Normal file
24
hiro/gtk/widget/progress-bar.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pProgressBar::construct() -> void {
|
||||
gtkWidget = gtk_progress_bar_new();
|
||||
|
||||
setPosition(state().position);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pProgressBar::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pProgressBar::minimumSize() const -> Size {
|
||||
return {0, 25};
|
||||
}
|
||||
|
||||
auto pProgressBar::setPosition(unsigned position) -> void {
|
||||
position = position <= 100 ? position : 0;
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkWidget), (double)position / 100.0);
|
||||
}
|
||||
|
||||
}
|
10
hiro/gtk/widget/progress-bar.hpp
Normal file
10
hiro/gtk/widget/progress-bar.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pProgressBar : pWidget {
|
||||
Declare(ProgressBar, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setPosition(unsigned position) -> void;
|
||||
};
|
||||
|
||||
}
|
101
hiro/gtk/widget/radio-button.cpp
Normal file
101
hiro/gtk/widget/radio-button.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto RadioButton_activate(GtkToggleButton*, pRadioButton* p) -> void {
|
||||
if(p->_parent().locked()) return;
|
||||
bool wasChecked = p->state().checked;
|
||||
p->setChecked();
|
||||
if(!wasChecked) p->self().doActivate();
|
||||
}
|
||||
|
||||
auto pRadioButton::construct() -> void {
|
||||
gtkWidget = gtk_toggle_button_new();
|
||||
|
||||
setGroup(state().group);
|
||||
setBordered(state().bordered);
|
||||
setIcon(state().icon);
|
||||
setOrientation(state().orientation);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(RadioButton_activate), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pRadioButton::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pRadioButton::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
|
||||
if(state().orientation == Orientation::Horizontal) {
|
||||
size.setWidth(size.width() + state().icon.width);
|
||||
size.setHeight(max(size.height(), state().icon.height));
|
||||
}
|
||||
|
||||
if(state().orientation == Orientation::Vertical) {
|
||||
size.setWidth(max(size.width(), state().icon.width));
|
||||
size.setHeight(size.height() + state().icon.height);
|
||||
}
|
||||
|
||||
return {size.width() + 24, size.height() + 12};
|
||||
}
|
||||
|
||||
auto pRadioButton::setBordered(bool bordered) -> void {
|
||||
gtk_button_set_relief(GTK_BUTTON(gtkWidget), bordered ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
|
||||
}
|
||||
|
||||
auto pRadioButton::setChecked() -> void {
|
||||
_parent().lock();
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self()) {
|
||||
bool checked = item->self() == this;
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), item->state.checked = checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pRadioButton::setGroup(const vector<wRadioButton>& group) -> void {
|
||||
_parent().lock();
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self()) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), item->checked());
|
||||
}
|
||||
}
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pRadioButton::setIcon(const image& icon) -> void {
|
||||
if(icon) {
|
||||
GtkImage* gtkImage = CreateImage(icon);
|
||||
gtk_button_set_image(GTK_BUTTON(gtkWidget), (GtkWidget*)gtkImage);
|
||||
} else {
|
||||
gtk_button_set_image(GTK_BUTTON(gtkWidget), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto pRadioButton::setOrientation(Orientation orientation) -> void {
|
||||
switch(orientation) {
|
||||
case Orientation::Horizontal: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_LEFT); break;
|
||||
case Orientation::Vertical: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_TOP); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto pRadioButton::setText(const string& text) -> void {
|
||||
gtk_button_set_label(GTK_BUTTON(gtkWidget), text);
|
||||
setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font
|
||||
}
|
||||
|
||||
auto pRadioButton::_parent() -> pRadioButton& {
|
||||
if(state().group.size()) {
|
||||
if(auto item = state().group.first().acquire()) {
|
||||
if(item->self()) return *item->self();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
17
hiro/gtk/widget/radio-button.hpp
Normal file
17
hiro/gtk/widget/radio-button.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pRadioButton : pWidget {
|
||||
Declare(RadioButton, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setBordered(bool bordered) -> void;
|
||||
auto setChecked() -> void;
|
||||
auto setGroup(const vector<wRadioButton>& group) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setOrientation(Orientation orientation) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _parent() -> pRadioButton&;
|
||||
};
|
||||
|
||||
}
|
71
hiro/gtk/widget/radio-label.cpp
Normal file
71
hiro/gtk/widget/radio-label.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto RadioLabel_activate(GtkToggleButton*, pRadioLabel* p) -> void {
|
||||
if(p->_parent().locked()) return;
|
||||
bool wasChecked = p->state().checked;
|
||||
p->setChecked();
|
||||
if(!wasChecked) p->self().doActivate();
|
||||
}
|
||||
|
||||
auto pRadioLabel::construct() -> void {
|
||||
gtkWidget = gtk_radio_button_new_with_label(nullptr, "");
|
||||
|
||||
setGroup(state().group);
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(RadioLabel_activate), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pRadioLabel::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pRadioLabel::minimumSize() const -> Size {
|
||||
Size size = pFont::size(self().font(true), state().text);
|
||||
return {size.width() + 28, size.height() + 4};
|
||||
}
|
||||
|
||||
auto pRadioLabel::setChecked() -> void {
|
||||
_parent().lock();
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) item->state.checked = false;
|
||||
}
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), state().checked = true);
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pRadioLabel::setGroup(const vector<shared_pointer_weak<mRadioLabel>>& group) -> void {
|
||||
if(&_parent() == this) return;
|
||||
_parent().lock();
|
||||
gtk_radio_button_set_group(
|
||||
GTK_RADIO_BUTTON(gtkWidget),
|
||||
gtk_radio_button_get_group(GTK_RADIO_BUTTON(_parent().gtkWidget))
|
||||
);
|
||||
for(auto& weak : state().group) {
|
||||
if(auto item = weak.acquire()) {
|
||||
if(item->self() && item->checked()) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item->self()->gtkWidget), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_parent().unlock();
|
||||
}
|
||||
|
||||
auto pRadioLabel::setText(const string& text) -> void {
|
||||
gtk_button_set_label(GTK_BUTTON(gtkWidget), text);
|
||||
setFont(self().font(true)); //gtk_button_set_label() recreates label, which destroys currently assigned font
|
||||
}
|
||||
|
||||
auto pRadioLabel::_parent() -> pRadioLabel& {
|
||||
if(state().group.size()) {
|
||||
if(auto item = state().group.first().acquire()) {
|
||||
if(item->self()) return *item->self();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
14
hiro/gtk/widget/radio-label.hpp
Normal file
14
hiro/gtk/widget/radio-label.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pRadioLabel : pWidget {
|
||||
Declare(RadioLabel, Widget)
|
||||
|
||||
auto minimumSize() const -> Size;
|
||||
auto setChecked() -> void;
|
||||
auto setGroup(const vector<shared_pointer_weak<mRadioLabel>>& group) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _parent() -> pRadioLabel&;
|
||||
};
|
||||
|
||||
}
|
126
hiro/gtk/widget/source-edit.cpp
Normal file
126
hiro/gtk/widget/source-edit.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto SourceEdit_change(GtkTextBuffer*, pSourceEdit* p) -> void {
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
static auto SourceEdit_move(GObject*, GParamSpec*, pSourceEdit* p) -> void {
|
||||
signed position = 0;
|
||||
g_object_get(G_OBJECT(p->gtkSourceBuffer), "cursor-position", &position, nullptr);
|
||||
|
||||
if(p->state().position != position) {
|
||||
p->state().position = position;
|
||||
if(!p->locked()) p->self().doMove();
|
||||
}
|
||||
}
|
||||
|
||||
auto pSourceEdit::construct() -> void {
|
||||
gtkScrolledWindow = (GtkScrolledWindow*)gtk_scrolled_window_new(0, 0);
|
||||
gtkContainer = GTK_CONTAINER(gtkScrolledWindow);
|
||||
gtkWidget = GTK_WIDGET(gtkScrolledWindow);
|
||||
gtk_scrolled_window_set_policy(gtkScrolledWindow, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type(gtkScrolledWindow, GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
gtkSourceLanguageManager = gtk_source_language_manager_get_default();
|
||||
gtkSourceLanguage = gtk_source_language_manager_get_language(gtkSourceLanguageManager, "cpp");
|
||||
|
||||
gtkSourceStyleSchemeManager = gtk_source_style_scheme_manager_get_default();
|
||||
gtkSourceStyleScheme = gtk_source_style_scheme_manager_get_scheme(gtkSourceStyleSchemeManager, "oblivion");
|
||||
|
||||
gtkSourceBuffer = gtk_source_buffer_new(nullptr);
|
||||
gtkTextBuffer = GTK_TEXT_BUFFER(gtkSourceBuffer);
|
||||
gtk_source_buffer_set_highlight_matching_brackets(gtkSourceBuffer, true);
|
||||
gtk_source_buffer_set_highlight_syntax(gtkSourceBuffer, true);
|
||||
//gtk_source_buffer_set_language(gtkSourceBuffer, gtkSourceLanguage);
|
||||
gtk_source_buffer_set_style_scheme(gtkSourceBuffer, gtkSourceStyleScheme);
|
||||
|
||||
gtkSourceView = (GtkSourceView*)gtk_source_view_new_with_buffer(gtkSourceBuffer);
|
||||
gtkTextView = GTK_TEXT_VIEW(gtkSourceView);
|
||||
gtkWidgetSourceView = GTK_WIDGET(gtkSourceView);
|
||||
gtk_source_view_set_auto_indent(gtkSourceView, false);
|
||||
gtk_source_view_set_draw_spaces(gtkSourceView, (GtkSourceDrawSpacesFlags)0);
|
||||
gtk_source_view_set_highlight_current_line(gtkSourceView, true);
|
||||
gtk_source_view_set_indent_on_tab(gtkSourceView, false);
|
||||
gtk_source_view_set_indent_width(gtkSourceView, 4);
|
||||
gtk_source_view_set_insert_spaces_instead_of_tabs(gtkSourceView, false);
|
||||
gtk_source_view_set_right_margin_position(gtkSourceView, 80);
|
||||
gtk_source_view_set_show_line_marks(gtkSourceView, false);
|
||||
gtk_source_view_set_show_line_numbers(gtkSourceView, true);
|
||||
gtk_source_view_set_show_right_margin(gtkSourceView, true);
|
||||
gtk_source_view_set_smart_home_end(gtkSourceView, GTK_SOURCE_SMART_HOME_END_DISABLED);
|
||||
gtk_source_view_set_tab_width(gtkSourceView, 4);
|
||||
gtk_container_add(gtkContainer, gtkWidgetSourceView);
|
||||
gtk_widget_show(gtkWidgetSourceView);
|
||||
|
||||
setText(state().text);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkSourceBuffer), "changed", G_CALLBACK(SourceEdit_change), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkSourceBuffer), "notify::cursor-position", G_CALLBACK(SourceEdit_move), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pSourceEdit::destruct() -> void {
|
||||
state().text = text();
|
||||
gtk_widget_destroy(gtkWidgetSourceView);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pSourceEdit::setFocused() -> void {
|
||||
gtk_widget_grab_focus(gtkWidgetSourceView);
|
||||
}
|
||||
|
||||
auto pSourceEdit::setPosition(signed position) -> void {
|
||||
lock();
|
||||
GtkTextIter iter;
|
||||
//note: iterators must be initialized via get_iter() before calling set_offset()
|
||||
gtk_text_buffer_get_end_iter(gtkTextBuffer, &iter);
|
||||
if(position >= 0) {
|
||||
gtk_text_iter_set_offset(&iter, position);
|
||||
} else {
|
||||
state().position = gtk_text_iter_get_offset(&iter);
|
||||
}
|
||||
gtk_text_buffer_place_cursor(gtkTextBuffer, &iter);
|
||||
auto mark = gtk_text_buffer_get_mark(gtkTextBuffer, "insert");
|
||||
gtk_text_view_scroll_mark_onscreen(gtkTextView, mark);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pSourceEdit::setSelected(Position selected) -> void {
|
||||
lock();
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_end_iter(gtkTextBuffer, &iter);
|
||||
signed offset = gtk_text_iter_get_offset(&iter);
|
||||
if(selected.x() < 0 || selected.x() > offset) selected.setX(offset);
|
||||
if(selected.y() < 0 || selected.y() > offset) selected.setY(offset);
|
||||
state().selected = selected;
|
||||
GtkTextIter startIter;
|
||||
gtk_text_buffer_get_start_iter(gtkTextBuffer, &startIter);
|
||||
gtk_text_iter_set_offset(&startIter, selected.x());
|
||||
GtkTextIter endIter;
|
||||
gtk_text_buffer_get_end_iter(gtkTextBuffer, &endIter);
|
||||
gtk_text_iter_set_offset(&endIter, selected.y());
|
||||
gtk_text_buffer_select_range(gtkTextBuffer, &startIter, &endIter);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pSourceEdit::setText(const string& text) -> void {
|
||||
lock();
|
||||
gtk_text_buffer_set_text(gtkTextBuffer, text, -1);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pSourceEdit::text() const -> string {
|
||||
GtkTextIter startIter;
|
||||
gtk_text_buffer_get_start_iter(gtkTextBuffer, &startIter);
|
||||
|
||||
GtkTextIter endIter;
|
||||
gtk_text_buffer_get_end_iter(gtkTextBuffer, &endIter);
|
||||
|
||||
char* textBuffer = gtk_text_buffer_get_text(gtkTextBuffer, &startIter, &endIter, true);
|
||||
string text = textBuffer;
|
||||
g_free(textBuffer);
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
25
hiro/gtk/widget/source-edit.hpp
Normal file
25
hiro/gtk/widget/source-edit.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pSourceEdit : pWidget {
|
||||
Declare(SourceEdit, Widget)
|
||||
|
||||
auto setFocused() -> void override;
|
||||
auto setPosition(signed position) -> void;
|
||||
auto setSelected(Position selected) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
auto text() const -> string;
|
||||
|
||||
GtkScrolledWindow* gtkScrolledWindow = nullptr;
|
||||
GtkContainer* gtkContainer = nullptr;
|
||||
GtkSourceBuffer* gtkSourceBuffer = nullptr;
|
||||
GtkTextBuffer* gtkTextBuffer = nullptr;
|
||||
GtkSourceLanguageManager* gtkSourceLanguageManager = nullptr;
|
||||
GtkSourceLanguage* gtkSourceLanguage = nullptr;
|
||||
GtkSourceStyleSchemeManager* gtkSourceStyleSchemeManager = nullptr;
|
||||
GtkSourceStyleScheme* gtkSourceStyleScheme = nullptr;
|
||||
GtkSourceView* gtkSourceView = nullptr;
|
||||
GtkTextView* gtkTextView = nullptr;
|
||||
GtkWidget* gtkWidgetSourceView = nullptr;
|
||||
};
|
||||
|
||||
}
|
46
hiro/gtk/widget/tab-frame-item.cpp
Normal file
46
hiro/gtk/widget/tab-frame-item.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace hiro {
|
||||
|
||||
auto pTabFrameItem::construct() -> void {
|
||||
if(auto layout = state().layout) layout->construct();
|
||||
}
|
||||
|
||||
auto pTabFrameItem::destruct() -> void {
|
||||
if(auto layout = state().layout) layout->destruct();
|
||||
}
|
||||
|
||||
auto pTabFrameItem::setClosable(bool closable) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemClosable(self().offset(), closable);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrameItem::setIcon(const image& icon) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemIcon(self().offset(), icon);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrameItem::setMovable(bool movable) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemMovable(self().offset(), movable);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrameItem::setSelected() -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemSelected(self().offset());
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrameItem::setText(const string& text) -> void {
|
||||
if(auto parent = _parent()) {
|
||||
parent->setItemText(self().offset(), text);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrameItem::_parent() -> pTabFrame* {
|
||||
if(auto parent = self().parentTabFrame()) return parent->self();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
15
hiro/gtk/widget/tab-frame-item.hpp
Normal file
15
hiro/gtk/widget/tab-frame-item.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pTabFrameItem : pObject {
|
||||
Declare(TabFrameItem, Object)
|
||||
|
||||
auto setClosable(bool closable) -> void;
|
||||
auto setIcon(const image& icon) -> void;
|
||||
auto setMovable(bool movable) -> void;
|
||||
auto setSelected() -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
|
||||
auto _parent() -> pTabFrame*;
|
||||
};
|
||||
|
||||
}
|
265
hiro/gtk/widget/tab-frame.cpp
Normal file
265
hiro/gtk/widget/tab-frame.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto TabFrame_change(GtkNotebook* notebook, GtkWidget* page, unsigned position, pTabFrame* p) -> void {
|
||||
p->state().selected = position;
|
||||
p->_synchronizeLayout();
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
static auto TabFrame_close(GtkButton* button, pTabFrame* p) -> void {
|
||||
maybe<unsigned> position;
|
||||
for(auto n : range(p->tabs)) {
|
||||
if(button == (GtkButton*)p->tabs[n].close) {
|
||||
position = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(position) {
|
||||
if(!p->locked()) p->self().doClose(p->self().item(*position));
|
||||
}
|
||||
}
|
||||
|
||||
static auto TabFrame_move(GtkNotebook* notebook, GtkWidget* page, unsigned moveTo, pTabFrame* p) -> void {
|
||||
p->state().selected = gtk_notebook_get_current_page(notebook);
|
||||
maybe<unsigned> moveFrom;
|
||||
for(auto n : range(p->tabs)) {
|
||||
if(page == p->tabs[n].child) {
|
||||
moveFrom = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(moveFrom) {
|
||||
p->state().items.insert(moveTo, p->state().items.take(*moveFrom));
|
||||
p->tabs.insert(moveTo, p->tabs.take(*moveFrom));
|
||||
if(!p->locked()) p->self().doMove(p->self().item(*moveFrom), p->self().item(moveTo));
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrame::construct() -> void {
|
||||
gtkWidget = gtk_notebook_new();
|
||||
gtk_notebook_set_show_border(GTK_NOTEBOOK(gtkWidget), false);
|
||||
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkWidget), GTK_POS_TOP);
|
||||
|
||||
tabs.reset(); //todo: memory leak, need to release each tab
|
||||
for(auto& item : state().items) append(item);
|
||||
setEdge(state().edge);
|
||||
setItemSelected(state().selected);
|
||||
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "page-reordered", G_CALLBACK(TabFrame_move), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "switch-page", G_CALLBACK(TabFrame_change), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pTabFrame::destruct() -> void {
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pTabFrame::append(sTabFrameItem item) -> void {
|
||||
lock();
|
||||
Tab tab;
|
||||
tab.child = gtk_fixed_new();
|
||||
tab.container = gtk_hbox_new(false, 0);
|
||||
tab.image = gtk_image_new();
|
||||
tab.title = gtk_label_new("");
|
||||
gtk_misc_set_alignment(GTK_MISC(tab.title), 0.0, 0.5);
|
||||
tab.close = gtk_button_new_with_label("\u00d7"); //Unicode multiplication sign (looks better than 'X')
|
||||
gtk_button_set_focus_on_click(GTK_BUTTON(tab.close), false);
|
||||
gtk_button_set_relief(GTK_BUTTON(tab.close), GTK_RELIEF_NONE);
|
||||
pFont::setFont(tab.close, Font::sans(9, "Bold"));
|
||||
auto color = CreateColor({255, 0, 0});
|
||||
gtk_widget_modify_fg(gtk_bin_get_child(GTK_BIN(tab.close)), GTK_STATE_PRELIGHT, &color);
|
||||
tabs.append(tab);
|
||||
|
||||
gtk_widget_show(tab.child);
|
||||
gtk_widget_show(tab.container);
|
||||
gtk_widget_show(tab.image);
|
||||
gtk_widget_show(tab.title);
|
||||
gtk_widget_show(tab.close);
|
||||
gtk_box_pack_start(GTK_BOX(tab.container), tab.image, false, false, 0);
|
||||
gtk_box_pack_start(GTK_BOX(tab.container), tab.title, true, true, 0);
|
||||
gtk_box_pack_start(GTK_BOX(tab.container), tab.close, false, false, 0);
|
||||
|
||||
g_signal_connect(G_OBJECT(tab.close), "clicked", G_CALLBACK(TabFrame_close), (gpointer)this);
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(gtkWidget), tab.child, tab.container);
|
||||
|
||||
setFont(self().font(true));
|
||||
setItemMovable(item->offset(), item->movable());
|
||||
_synchronizeTab(tabs.size() - 1);
|
||||
setGeometry(self().geometry());
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTabFrame::container(mWidget& widget) -> GtkWidget* {
|
||||
auto widgetLayout = widget.parentLayout();
|
||||
unsigned position = 0;
|
||||
for(auto& item : state().items) {
|
||||
if(item->state.layout.data() == widgetLayout) return tabs[position].child;
|
||||
position++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pTabFrame::remove(sTabFrameItem item) -> void {
|
||||
lock();
|
||||
//if we are removing the current tab, we have to select another tab manually
|
||||
if(item->offset() == gtk_notebook_get_current_page(GTK_NOTEBOOK(gtkWidget))) {
|
||||
//the new tab will be the one after this one
|
||||
unsigned displacement = 1;
|
||||
//... unless it's the last tab, in which case it's the one before it
|
||||
if(item->offset() == self().items() - 1) displacement = -1;
|
||||
//... unless there are no tabs left, in which case nothing is selected
|
||||
if(self().items() > 1) {
|
||||
setItemSelected(item->offset() + displacement);
|
||||
}
|
||||
}
|
||||
tabs.remove(item->offset());
|
||||
gtk_notebook_remove_page(GTK_NOTEBOOK(gtkWidget), item->offset());
|
||||
state().selected = gtk_notebook_get_current_page(GTK_NOTEBOOK(gtkWidget));
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTabFrame::setEdge(Edge edge) -> void {
|
||||
GtkPositionType type;
|
||||
switch(edge) { default:
|
||||
case Edge::Top: type = GTK_POS_TOP; break;
|
||||
case Edge::Bottom: type = GTK_POS_BOTTOM; break;
|
||||
case Edge::Left: type = GTK_POS_LEFT; break;
|
||||
case Edge::Right: type = GTK_POS_RIGHT; break;
|
||||
}
|
||||
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkWidget), type);
|
||||
setGeometry(self().geometry());
|
||||
}
|
||||
|
||||
auto pTabFrame::setEnabled(bool enabled) -> void {
|
||||
for(auto& item : state().items) {
|
||||
if(auto layout = item->state.layout) {
|
||||
if(layout->self()) layout->self()->setEnabled(layout->enabled(true));
|
||||
}
|
||||
}
|
||||
pWidget::setEnabled(enabled);
|
||||
}
|
||||
|
||||
auto pTabFrame::setFont(const string& font) -> void {
|
||||
for(auto n : range(tabs.size())) {
|
||||
pFont::setFont(tabs[n].title, font);
|
||||
if(auto layout = state().items[n]->state.layout) {
|
||||
if(layout->self()) layout->self()->setFont(layout->font(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrame::setGeometry(Geometry geometry) -> void {
|
||||
pWidget::setGeometry(geometry);
|
||||
|
||||
geometry.setPosition(0, 0);
|
||||
if(state().edge == Edge::Top || state().edge == Edge::Bottom) {
|
||||
geometry.setWidth(geometry.width() - 6);
|
||||
geometry.setHeight(geometry.height() - (15 + _tabHeight()));
|
||||
} else {
|
||||
geometry.setWidth(geometry.width() - (17 + _tabWidth()));
|
||||
geometry.setHeight(geometry.height() - 6);
|
||||
}
|
||||
for(auto& item : state().items) {
|
||||
if(item->state.layout) item->state.layout->setGeometry(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemClosable(unsigned position, bool closable) -> void {
|
||||
_synchronizeTab(position);
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemIcon(unsigned position, const image& icon) -> void {
|
||||
_synchronizeTab(position);
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemLayout(unsigned position, shared_pointer<mLayout> layout) -> void {
|
||||
//if(layout->self()) layout->self()->setParent();
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemMovable(unsigned position, bool movable) -> void {
|
||||
lock();
|
||||
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(gtkWidget), tabs[position].child, movable);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemSelected(unsigned position) -> void {
|
||||
lock();
|
||||
gtk_notebook_set_current_page(GTK_NOTEBOOK(gtkWidget), position);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTabFrame::setItemText(unsigned position, const string& text) -> void {
|
||||
_synchronizeTab(position);
|
||||
}
|
||||
|
||||
auto pTabFrame::setVisible(bool visible) -> void {
|
||||
for(auto& item : state().items) {
|
||||
if(auto layout = item->state.layout) {
|
||||
if(layout->self()) {
|
||||
layout->self()->setVisible(layout->visible(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
pWidget::setVisible(visible);
|
||||
}
|
||||
|
||||
auto pTabFrame::_synchronizeLayout() -> void {
|
||||
unsigned position = 0;
|
||||
for(auto& item : state().items) {
|
||||
if(auto layout = item->state.layout) {
|
||||
if(layout->self()) {
|
||||
layout->self()->setVisible(layout->visible(true) && position == state().selected);
|
||||
}
|
||||
}
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
auto pTabFrame::_synchronizeTab(unsigned position) -> void {
|
||||
auto& item = state().items[position];
|
||||
auto& tab = tabs[position];
|
||||
gtk_widget_set_visible(tab.close, item->closable());
|
||||
if(auto copy = item->state.icon) {
|
||||
unsigned size = pFont::size(self().font(true), " ").height();
|
||||
copy.scale(size, size);
|
||||
auto pixbuf = CreatePixbuf(copy);
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(tab.image), pixbuf);
|
||||
} else {
|
||||
gtk_image_clear(GTK_IMAGE(tab.image));
|
||||
}
|
||||
string text = {
|
||||
item->state.icon && item->state.text ? " " : "",
|
||||
item->state.text,
|
||||
item->state.text && item->state.closable ? " " : ""
|
||||
};
|
||||
gtk_label_set_text(GTK_LABEL(tab.title), text);
|
||||
}
|
||||
|
||||
//compute the height of the tallest tab for child layout geometry calculations
|
||||
auto pTabFrame::_tabHeight() -> unsigned {
|
||||
signed height = 1;
|
||||
|
||||
for(auto n : range(self().items())) {
|
||||
height = max(height, tabs[n].image->allocation.height);
|
||||
height = max(height, tabs[n].title->allocation.height);
|
||||
if(!state().items[n]->closable()) continue;
|
||||
height = max(height, tabs[n].close->allocation.height);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
auto pTabFrame::_tabWidth() -> unsigned {
|
||||
signed width = 1;
|
||||
|
||||
for(auto n : range(self().items())) {
|
||||
width = max(width, tabs[n].image->allocation.width + tabs[n].title->allocation.width +
|
||||
(state().items[n]->closable() ? tabs[n].close->allocation.width : 0)
|
||||
);
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
}
|
39
hiro/gtk/widget/tab-frame.hpp
Normal file
39
hiro/gtk/widget/tab-frame.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pTabFrame : pWidget {
|
||||
Declare(TabFrame, Widget)
|
||||
|
||||
auto append(sTabFrameItem item) -> void;
|
||||
auto container(mWidget& widget) -> GtkWidget* override;
|
||||
auto remove(sTabFrameItem item) -> void;
|
||||
auto setEdge(Edge edge) -> void;
|
||||
auto setEnabled(bool enabled) -> void override;
|
||||
auto setFont(const string& font) -> void override;
|
||||
auto setGeometry(Geometry geometry) -> void override;
|
||||
auto setItemClosable(unsigned position, bool closable) -> void;
|
||||
auto setItemIcon(unsigned position, const image& icon) -> void;
|
||||
auto setItemLayout(unsigned position, sLayout layout) -> void;
|
||||
auto setItemMovable(unsigned position, bool movable) -> void;
|
||||
auto setItemSelected(unsigned position) -> void;
|
||||
auto setItemText(unsigned position, const string& text) -> void;
|
||||
auto setVisible(bool visible) -> void override;
|
||||
|
||||
auto _append(shared_pointer<mTabFrameItem> item) -> void;
|
||||
auto _remove(shared_pointer<mTabFrameItem> item) -> void;
|
||||
auto _synchronizeLayout() -> void;
|
||||
auto _synchronizeTab(unsigned position) -> void;
|
||||
auto _tabHeight() -> unsigned;
|
||||
auto _tabWidth() -> unsigned;
|
||||
|
||||
struct Tab {
|
||||
GtkWidget* child = nullptr;
|
||||
GtkWidget* container = nullptr;
|
||||
GtkWidget* layout = nullptr;
|
||||
GtkWidget* image = nullptr;
|
||||
GtkWidget* title = nullptr;
|
||||
GtkWidget* close = nullptr;
|
||||
};
|
||||
vector<Tab> tabs;
|
||||
};
|
||||
|
||||
}
|
100
hiro/gtk/widget/text-edit.cpp
Normal file
100
hiro/gtk/widget/text-edit.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace hiro {
|
||||
|
||||
static auto TextEdit_change(GtkTextBuffer* textBuffer, pTextEdit* p) -> void {
|
||||
if(!p->locked()) p->self().doChange();
|
||||
}
|
||||
|
||||
static auto TextEdit_move(GObject* object, GParamSpec* spec, pTextEdit* p) -> void {
|
||||
int position = 0;
|
||||
g_object_get(p->textBuffer, "cursor-position", &position, nullptr);
|
||||
|
||||
if(p->state().cursorPosition != position) {
|
||||
p->state().cursorPosition = position;
|
||||
if(!p->locked()) p->self().doMove();
|
||||
}
|
||||
}
|
||||
|
||||
auto pTextEdit::construct() -> void {
|
||||
gtkWidget = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN);
|
||||
|
||||
subWidget = gtk_text_view_new();
|
||||
gtk_widget_show(subWidget);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_WORD_CHAR);
|
||||
gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget);
|
||||
|
||||
textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget));
|
||||
|
||||
setBackgroundColor(state().backgroundColor);
|
||||
setEditable(state().editable);
|
||||
setForegroundColor(state().foregroundColor);
|
||||
setText(state().text);
|
||||
setWordWrap(state().wordWrap);
|
||||
|
||||
g_signal_connect(G_OBJECT(textBuffer), "changed", G_CALLBACK(TextEdit_change), (gpointer)this);
|
||||
g_signal_connect(G_OBJECT(textBuffer), "notify::cursor-position", G_CALLBACK(TextEdit_move), (gpointer)this);
|
||||
|
||||
pWidget::construct();
|
||||
}
|
||||
|
||||
auto pTextEdit::destruct() -> void {
|
||||
state().text = text();
|
||||
gtk_widget_destroy(subWidget);
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
auto pTextEdit::focused() const -> bool {
|
||||
return GTK_WIDGET_HAS_FOCUS(subWidget);
|
||||
}
|
||||
|
||||
auto pTextEdit::setBackgroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_base(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pTextEdit::setCursorPosition(unsigned position) -> void {
|
||||
lock();
|
||||
GtkTextMark* mark = gtk_text_buffer_get_mark(textBuffer, "insert");
|
||||
GtkTextIter iter;
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &iter);
|
||||
gtk_text_iter_set_offset(&iter, min(position, gtk_text_iter_get_offset(&iter)));
|
||||
gtk_text_buffer_place_cursor(textBuffer, &iter);
|
||||
gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(subWidget), mark);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTextEdit::setEditable(bool editable) -> void {
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), editable);
|
||||
}
|
||||
|
||||
auto pTextEdit::setForegroundColor(Color color) -> void {
|
||||
GdkColor gdkColor = CreateColor(color);
|
||||
gtk_widget_modify_text(subWidget, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
||||
}
|
||||
|
||||
auto pTextEdit::setText(const string& text) -> void {
|
||||
lock();
|
||||
textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget));
|
||||
gtk_text_buffer_set_text(textBuffer, text, -1);
|
||||
unlock();
|
||||
}
|
||||
|
||||
auto pTextEdit::setWordWrap(bool wordWrap) -> void {
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget),
|
||||
wordWrap ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS,
|
||||
GTK_POLICY_ALWAYS);
|
||||
}
|
||||
|
||||
auto pTextEdit::text() const -> string {
|
||||
GtkTextIter start, end;
|
||||
gtk_text_buffer_get_start_iter(textBuffer, &start);
|
||||
gtk_text_buffer_get_end_iter(textBuffer, &end);
|
||||
char* temp = gtk_text_buffer_get_text(textBuffer, &start, &end, true);
|
||||
string text = temp;
|
||||
g_free(temp);
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
19
hiro/gtk/widget/text-edit.hpp
Normal file
19
hiro/gtk/widget/text-edit.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace hiro {
|
||||
|
||||
struct pTextEdit : pWidget {
|
||||
Declare(TextEdit, Widget)
|
||||
|
||||
auto focused() const -> bool override;
|
||||
auto setBackgroundColor(Color color) -> void;
|
||||
auto setCursorPosition(unsigned position) -> void;
|
||||
auto setEditable(bool editable) -> void;
|
||||
auto setForegroundColor(Color color) -> void;
|
||||
auto setText(const string& text) -> void;
|
||||
auto setWordWrap(bool wordWrap) -> void;
|
||||
auto text() const -> string;
|
||||
|
||||
GtkWidget* subWidget = nullptr;
|
||||
GtkTextBuffer* textBuffer = nullptr;
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user