771 lines
15 KiB
C++
Raw Normal View History

/***************************************************************************
gmenu.cpp
(c) 2004-2006 - Daniel Campos Fernández <dcamposf@gmail.com>
Gtkmae "GTK+ made easy" classes
Realizado para la Junta de Extremadura.
Consejería de Educación Ciencia y Tecnología.
Proyecto gnuLinEx
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***************************************************************************/
#include "widgets.h"
#include "widgets_private.h"
#include "gmainwindow.h"
#include "gmenu.h"
typedef
struct {
int x;
int y;
}
MenuPosition;
static GList *menus=NULL;
static void mnu_destroy (GtkWidget *object,gMenu *data)
{
if (data->stop_signal)
data->stop_signal = false;
else
delete data;
}
static void mnu_activate(GtkMenuItem *menuitem,gMenu *data)
{
if (data->onClick) data->onClick(data);
}
static gboolean cb_map(GtkWidget *menu, gMenu *data)
{
data->hideSeparators();
if (data->onShow) (*data->onShow)(data);
return false;
}
static gboolean cb_unmap(GtkWidget *menu, gMenu *data)
{
if (data->onHide) (*data->onHide)(data);
return false;
}
static int get_menu_pos(GtkWidget *menu)
{
GtkMenuShell *par;
GList *iter;
int pos;
if (!menu->parent)
{
//g_debug("get_menu_pos: no parent for menu %p", menu);
return -1;
}
par = (GtkMenuShell*)(menu->parent);
iter = g_list_first(par->children);
for(pos = 0;; pos++)
{
if (iter->data == (gpointer)menu)
break;
iter = g_list_next(iter);
}
return pos;
}
static gboolean cb_check_expose(GtkWidget *wid, GdkEventExpose *e, gMenu *menu)
{
int x, y, w, h;
if (menu->checked())
{
x = wid->allocation.x;
y = wid->allocation.y;
w = wid->allocation.width;
h = wid->allocation.height;
if (h > w)
{
y += (h - w) / 2;
h = w;
}
else if (w > h)
{
x += (w - h) / 2;
w = h;
}
gtk_paint_check(wid->style, wid->window, (GtkStateType)GTK_WIDGET_STATE(wid), GTK_SHADOW_IN,
&e->area, NULL, NULL,
x + 1, y + 1, w - 2, h - 2);
}
return false;
}
void gMenu::update()
{
GtkWidget *hbox;
GtkMenuShell *shell = NULL;
gint pos;
GtkRequisition req;
if (_no_update)
return;
//g_debug("%p: START UPDATE (menu = %p child = %p parent = %p)", this, menu);
if (_style != _oldstyle)
{
if (child)
{
g_object_ref(G_OBJECT(child));
if (_style == MENU)
gtk_menu_item_remove_submenu(menu);
}
if (menu)
{
pos = get_menu_pos(GTK_WIDGET(menu));
//shell = (GtkMenuShell*)GTK_WIDGET(menu)->parent;
if (_style != NOTHING)
stop_signal = true;
gtk_widget_destroy(GTK_WIDGET(menu));
//g_debug("%p: delete old menu/separator", this);
}
else
{
pos = -1;
//shell = NULL;
}
if (_style != NOTHING)
{
if (_style == SEPARATOR)
{
menu = (GtkMenuItem *)gtk_separator_menu_item_new();
gtk_widget_size_request(GTK_WIDGET(menu), &req);
if (req.height > 5)
gtk_widget_set_size_request(GTK_WIDGET(menu), -1, 5);
//g_debug("%p: create new separator %p", this, menu);
}
else if (_style == MENU)
{
menu = (GtkMenuItem *)gtk_menu_item_new();
//g_debug("%p: create new menu %p", this, menu);
hbox = gtk_hbox_new(false, 4);
gtk_container_add(GTK_CONTAINER(menu), GTK_WIDGET(hbox));
label = gtk_label_new_with_mnemonic("");
//gtk_label_set_justify(GTK_LABEL(lbl),GTK_JUSTIFY_LEFT);
if (!top_level)
{
image = gtk_image_new();
aclbl = gtk_label_new("");
gtk_misc_set_alignment(GTK_MISC(aclbl), 0, 0.5);
gtk_size_group_add_widget(((gMenu*)pr)->sizeGroup, aclbl);
check = gtk_image_new();
gtk_widget_set_size_request(check, 16, 16);
g_signal_connect_after(G_OBJECT(check), "expose-event", G_CALLBACK(cb_check_expose), (gpointer)this);
gtk_box_pack_start(GTK_BOX(hbox), check, false, false, 0);
gtk_box_pack_start(GTK_BOX(hbox), image, false, false, 0);
gtk_box_pack_start(GTK_BOX(hbox), label, false, false, 0);
gtk_box_pack_end(GTK_BOX(hbox), aclbl, false, false, 0);
}
else
{
check = NULL;
image = NULL;
aclbl = NULL;
gtk_box_pack_start(GTK_BOX(hbox), label, false, false, 0);
}
if (child)
{
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu), GTK_WIDGET(child));
g_object_unref(G_OBJECT(child));
}
//set_gdk_fg_color(label, get_gdk_fg_color(GTK_WIDGET(shell)));
//set_gdk_bg_color(label, get_gdk_bg_color(GTK_WIDGET(shell)));
}
gtk_widget_show_all(GTK_WIDGET(menu));
if (top_level)
{
gMainWindow *win = (gMainWindow *)pr;
set_gdk_fg_color(GTK_WIDGET(menu), win->foreground());
set_gdk_bg_color(GTK_WIDGET(menu), win->background());
//gtk_menu_shell_append(GTK_MENU_SHELL(win->menuBar), GTK_WIDGET(menu));
shell = GTK_MENU_SHELL(win->menuBar);
}
else
{
gMenu *parent = (gMenu *)pr;
if (!parent->child)
{
parent->child = (GtkMenu*)gtk_menu_new();
//g_debug("%p: creates a new child menu container in parent %p", this, parent->child);
g_signal_connect(G_OBJECT(parent->child), "map", G_CALLBACK(cb_map), (gpointer)parent);
g_signal_connect(G_OBJECT(parent->child), "unmap", G_CALLBACK(cb_unmap), (gpointer)parent);
gtk_widget_show_all(GTK_WIDGET(parent->child));
if (parent->style() == MENU)
gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent->menu), GTK_WIDGET(parent->child));
//gtk_menu_shell_append(GTK_MENU_SHELL(parent->child), GTK_WIDGET(menu));
//g_debug("%p: add to new parent %p", this, parent->child);
}
shell = GTK_MENU_SHELL(parent->child);
}
if (shell)
{
if (pos < 0)
{
gtk_menu_shell_append(shell, GTK_WIDGET(menu));
//g_debug("%p: append to parent %p", this, shell);
}
else
{
gtk_menu_shell_insert(shell, GTK_WIDGET(menu), pos);
//g_debug("%p: insert into parent %p", this, shell);
}
}
g_signal_connect(G_OBJECT(menu), "destroy", G_CALLBACK(mnu_destroy), (gpointer)this);
g_signal_connect(G_OBJECT(menu), "activate", G_CALLBACK(mnu_activate), (gpointer)this);
}
_oldstyle = _style;
}
if (_style == MENU)
{
{
char *buf;
gMnemonic_correctText(_text, &buf);
gtk_label_set_text_with_mnemonic(GTK_LABEL(label), buf);
g_free(buf);
}
if (!top_level)
{
char *buf;
if (_shortcut)
{
******** Merged /branches/64bits r918:1003 into /trunk [CONFIGURATION] * NEW: 64 bits port. [EXAMPLES] * BUG: Fixed the AnalogWatch example. [WIKI CGI SCRIPT] * NEW: Some little cosmetic changes. [INTERPRETER] * NEW: The extern function implementation has been redesigned and is now based on libffi, so that it works on 64 bits system. Because of a flaw in the compiler design, projects that use the Pointer datatype must be recompiled to be used on a 64 bits system. This flaw will be fixed in Gambas 3. * OPT: Put some tables into read-only memory. About 1000 bytes are saved for each running interpreter, except the first one. * BUG: Does not crash anymore if a component cannot be loaded. * NEW: Spanish translation updated. * NEW: A new interpreter API for returning a pointer. [COMPILER] * BUG: Correctly compiles LONG constants inside code. [GB.DEBUG] * BUG: Compiles and links the gb.debug components with the thread libraries. [GB.DB.SQLITE3] * BUG: Getting the primary index of a table without primary index is safe now. [GB.GTK] * BUG: Modified the GLib priority of watched descriptors, as the main loop could enter in a loop in which user interface events were not managed. * BUG: Message boxes use application title without crashing now. [GB.OPENGL] * BUG: Disable dead code. [GB.QT.EXT] * BUG: TextEdit.TextWidth and TextEdit.TextHeight were not declared as read-only properties. [GB.XML.XSLT] * BUG: XSLT class is now declared as being not creatable. git-svn-id: svn://localhost/gambas/trunk@1006 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2008-01-17 21:39:26 +00:00
buf = g_strconcat(" ", _shortcut ," ",(void *)NULL);
gtk_label_set_text(GTK_LABEL(aclbl), buf);
g_free(buf);
}
else
gtk_label_set_text(GTK_LABEL(aclbl), " ");
}
if (!top_level)
{
GdkPixbuf *buf;
buf = _picture ? _picture->getPixbuf() : 0;
gtk_image_set_from_pixbuf(GTK_IMAGE(image), buf);
if (buf)
gtk_widget_show(image);
else
gtk_widget_hide(image);
if (_checked || !buf)
gtk_widget_show(check);
else
gtk_widget_hide(check);
}
else
{
//gtk_widget_hide(image);
//gtk_widget_hide(check);
}
}
//g_debug("%p: END UPDATE", this);
}
void gMenu::initialize()
{
//fprintf(stderr, "gMenu::gMenu: %p (%p)\n", this, pr);
stop_signal = false;
onFinish = NULL;
onClick = NULL;
onShow = NULL;
onHide = NULL;
hFree = NULL;
child = NULL;
image = NULL;
label = NULL;
aclbl = NULL;
check = NULL;
menu = NULL;
top_level=false;
_text = NULL;
_shortcut = NULL;
_checked = false;
_picture = NULL;
_name = NULL;
_toggle = false;
_style = NOTHING;
_oldstyle = NOTHING;
_no_update = false;
_destroyed = false;
_action = false;
sizeGroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
menus=g_list_append (menus,(gpointer)this);
}
gMenu::gMenu(gMainWindow *par,bool hidden)
{
//gControl *ctTop;
GtkVBox *box;
pr = (gpointer)par;
initialize();
top_level=true;
// BM: embedded windows can have menu bars
//ctTop=(gControl*)par;
//while (ctTop->pr) ctTop=ctTop->pr;
//par=(gMainWindow*)ctTop;
accel = par->accel;
g_object_ref(accel);
if (!par->menuBar)
{
//g_object_ref(par->widget);
//gtk_container_remove(GTK_CONTAINER(par->border),par->widget);
box=(GtkVBox*)gtk_vbox_new(false,0);
par->menuBar=(GtkMenuBar*)gtk_menu_bar_new();
gtk_box_pack_start(GTK_BOX(box),GTK_WIDGET(par->menuBar),false,true,0);
gtk_widget_reparent(par->widget,GTK_WIDGET(box));
gtk_container_add(GTK_CONTAINER(par->border),GTK_WIDGET(box));
//gtk_container_add(GTK_CONTAINER(box),par->widget);
gtk_widget_show_all(GTK_WIDGET(box));
//g_object_unref(par->widget);
gtk_widget_show_all(GTK_WIDGET(par->menuBar));
set_gdk_fg_color(GTK_WIDGET(par->menuBar),par->foreground());
set_gdk_bg_color(GTK_WIDGET(par->menuBar),par->background());
gtk_widget_modify_font(GTK_WIDGET(par->menuBar), par->font()->desc());
}
//update();
setText(" ");
}
gMenu::gMenu(gMenu *par, bool hidden)
{
pr=(gpointer)par;
initialize();
sizeGroup=gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
if (!par) return;
if (!par->menu) return;
accel = par->accel;
g_object_ref(accel);
//_style = SEPARATOR;
//update();
setText(NULL);
}
gMenu::~gMenu()
{
GList *item;
gMenu *mn;
if (_destroyed)
return;
//fprintf(stderr, "gMenu::~gMenu: %p (%p) '%s'\n", this, pr, _text);
setVisible(false);
_destroyed = true;
// Remove references to me
item = g_list_first(menus);
while (item)
{
mn = (gMenu*)item->data;
if (mn->pr == (void*)this)
mn->pr = NULL;
item = g_list_next(item);
}
_no_update = true;
setText(NULL);
setShortcut(NULL);
setPicture(NULL);
//if (_style != NOTHING)
{
if (aclbl && (!top_level) && pr)
gtk_size_group_remove_widget(((gMenu*)pr)->sizeGroup, aclbl);
if (child)
gtk_widget_destroy(GTK_WIDGET(child));
if (sizeGroup)
g_object_unref(G_OBJECT(sizeGroup));
if (accel)
g_object_unref(accel);
}
_style = NOTHING;
stop_signal = true;
if (menu)
gtk_widget_destroy(GTK_WIDGET(menu));
if (onFinish) onFinish(this);
menus = g_list_remove(menus, (gpointer)this);
}
bool gMenu::enabled()
{
return GTK_WIDGET_SENSITIVE(GTK_WIDGET(menu));
}
void gMenu::setEnabled(bool vl)
{
gtk_widget_set_sensitive(GTK_WIDGET(menu),vl);
}
void gMenu::setText(const char *text)
{
g_free(_text);
if (text)
_text = g_strdup(text);
else
_text = NULL;
if (!_text || !*_text)
_style = SEPARATOR;
else
_style = MENU;
update();
}
bool gMenu::isVisible()
{
if (!menu) return false;
return GTK_WIDGET_VISIBLE(menu);
}
void gMenu::setVisible(bool vl)
{
GtkContainer *par;
GList *chd,*iter;
if (!menu) return;
******** Merged /branches/64bits r918:1003 into /trunk [CONFIGURATION] * NEW: 64 bits port. [EXAMPLES] * BUG: Fixed the AnalogWatch example. [WIKI CGI SCRIPT] * NEW: Some little cosmetic changes. [INTERPRETER] * NEW: The extern function implementation has been redesigned and is now based on libffi, so that it works on 64 bits system. Because of a flaw in the compiler design, projects that use the Pointer datatype must be recompiled to be used on a 64 bits system. This flaw will be fixed in Gambas 3. * OPT: Put some tables into read-only memory. About 1000 bytes are saved for each running interpreter, except the first one. * BUG: Does not crash anymore if a component cannot be loaded. * NEW: Spanish translation updated. * NEW: A new interpreter API for returning a pointer. [COMPILER] * BUG: Correctly compiles LONG constants inside code. [GB.DEBUG] * BUG: Compiles and links the gb.debug components with the thread libraries. [GB.DB.SQLITE3] * BUG: Getting the primary index of a table without primary index is safe now. [GB.GTK] * BUG: Modified the GLib priority of watched descriptors, as the main loop could enter in a loop in which user interface events were not managed. * BUG: Message boxes use application title without crashing now. [GB.OPENGL] * BUG: Disable dead code. [GB.QT.EXT] * BUG: TextEdit.TextWidth and TextEdit.TextHeight were not declared as read-only properties. [GB.XML.XSLT] * BUG: XSLT class is now declared as being not creatable. git-svn-id: svn://localhost/gambas/trunk@1006 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2008-01-17 21:39:26 +00:00
g_object_set(G_OBJECT(menu),"visible",vl,(void *)NULL);
if (top_level && pr)
{
par=GTK_CONTAINER(((gMainWindow*)pr)->menuBar);
if (vl==true)
{
******** Merged /branches/64bits r918:1003 into /trunk [CONFIGURATION] * NEW: 64 bits port. [EXAMPLES] * BUG: Fixed the AnalogWatch example. [WIKI CGI SCRIPT] * NEW: Some little cosmetic changes. [INTERPRETER] * NEW: The extern function implementation has been redesigned and is now based on libffi, so that it works on 64 bits system. Because of a flaw in the compiler design, projects that use the Pointer datatype must be recompiled to be used on a 64 bits system. This flaw will be fixed in Gambas 3. * OPT: Put some tables into read-only memory. About 1000 bytes are saved for each running interpreter, except the first one. * BUG: Does not crash anymore if a component cannot be loaded. * NEW: Spanish translation updated. * NEW: A new interpreter API for returning a pointer. [COMPILER] * BUG: Correctly compiles LONG constants inside code. [GB.DEBUG] * BUG: Compiles and links the gb.debug components with the thread libraries. [GB.DB.SQLITE3] * BUG: Getting the primary index of a table without primary index is safe now. [GB.GTK] * BUG: Modified the GLib priority of watched descriptors, as the main loop could enter in a loop in which user interface events were not managed. * BUG: Message boxes use application title without crashing now. [GB.OPENGL] * BUG: Disable dead code. [GB.QT.EXT] * BUG: TextEdit.TextWidth and TextEdit.TextHeight were not declared as read-only properties. [GB.XML.XSLT] * BUG: XSLT class is now declared as being not creatable. git-svn-id: svn://localhost/gambas/trunk@1006 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2008-01-17 21:39:26 +00:00
g_object_set(G_OBJECT(par),"visible",vl,(void *)NULL);
return;
}
chd=gtk_container_get_children(par);
iter=g_list_first(chd);
while (iter)
{
if ( GTK_WIDGET_VISIBLE (GTK_WIDGET(iter->data)) )
{
g_list_free(chd);
return;
}
iter=g_list_next(iter);
}
g_list_free(chd);
******** Merged /branches/64bits r918:1003 into /trunk [CONFIGURATION] * NEW: 64 bits port. [EXAMPLES] * BUG: Fixed the AnalogWatch example. [WIKI CGI SCRIPT] * NEW: Some little cosmetic changes. [INTERPRETER] * NEW: The extern function implementation has been redesigned and is now based on libffi, so that it works on 64 bits system. Because of a flaw in the compiler design, projects that use the Pointer datatype must be recompiled to be used on a 64 bits system. This flaw will be fixed in Gambas 3. * OPT: Put some tables into read-only memory. About 1000 bytes are saved for each running interpreter, except the first one. * BUG: Does not crash anymore if a component cannot be loaded. * NEW: Spanish translation updated. * NEW: A new interpreter API for returning a pointer. [COMPILER] * BUG: Correctly compiles LONG constants inside code. [GB.DEBUG] * BUG: Compiles and links the gb.debug components with the thread libraries. [GB.DB.SQLITE3] * BUG: Getting the primary index of a table without primary index is safe now. [GB.GTK] * BUG: Modified the GLib priority of watched descriptors, as the main loop could enter in a loop in which user interface events were not managed. * BUG: Message boxes use application title without crashing now. [GB.OPENGL] * BUG: Disable dead code. [GB.QT.EXT] * BUG: TextEdit.TextWidth and TextEdit.TextHeight were not declared as read-only properties. [GB.XML.XSLT] * BUG: XSLT class is now declared as being not creatable. git-svn-id: svn://localhost/gambas/trunk@1006 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2008-01-17 21:39:26 +00:00
g_object_set(G_OBJECT(par),"visible",vl,(void *)NULL);
}
}
void gMenu::setPicture(gPicture *pic)
{
//fprintf(stderr, "gMenu::setPicture: %p\n", pic);
gPicture::assign(&_picture, pic);
update();
}
void gMenu::setChecked(bool vl)
{
_checked = vl;
update();
}
void gMenu::setToggle(bool vl)
{
_toggle = vl;
}
int gMenu::childCount()
{
GList *item;
gMenu *mn;
int ct=0;
if (!menus) return 0;
item=g_list_first(menus);
while (item)
{
mn=(gMenu*)item->data;
if (mn->pr == (void*)this) ct++;
item=g_list_next(item);
}
return ct;
}
gMenu* gMenu::childMenu(int pos)
{
GList *item;
gMenu *mn;
int ct=0;
if (!menus) return NULL;
item=g_list_first(menus);
while (item)
{
mn=(gMenu*)item->data;
if (mn->pr == (void*)this)
{
if (ct==pos) return mn;
ct++;
}
item=g_list_next(item);
}
return NULL;
}
void gMenu::destroy()
{
if (!_destroyed)
delete this;
}
void gMenu::popup()
{
guint32 time = gtk_get_current_event_time();
if (!child)
return;
gtk_menu_popup(child, NULL, NULL, NULL, NULL, 0, time);
}
static void position_menu(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, MenuPosition *pos)
{
*x = pos->x;
*y = pos->y;
*push_in = false;
delete pos;
}
void gMenu::popup(int x, int y)
{
guint32 time = gtk_get_current_event_time();
MenuPosition *pos;
if (!child)
return;
pos = new MenuPosition;
pos->x = x;
pos->y = y;
gtk_menu_popup(child, NULL, NULL, (GtkMenuPositionFunc)position_menu, (gpointer)pos, 0, time);
}
int gMenu::winChildCount(gMainWindow *par)
{
GList *item;
gMenu *mn;
int ct=0;
if (!menus) return 0;
item=g_list_first(menus);
while (item)
{
mn=(gMenu*)item->data;
if (mn->pr == (void*)par) ct++;
item=g_list_next(item);
}
return ct;
}
gMenu* gMenu::winChildMenu(gMainWindow *par,int pos)
{
GList *item;
gMenu *mn;
int ct=0;
if (!menus) return NULL;
item=g_list_first(menus);
while (item)
{
mn=(gMenu*)item->data;
if (mn->pr == (void*)par)
{
if (ct==pos) return mn;
ct++;
}
item=g_list_next(item);
}
return NULL;
}
void gMenu::setShortcut(char *shortcut)
{
guint key;
GdkModifierType mods;
if (_shortcut)
{
gt_shortcut_parse(_shortcut, &key, &mods);
if (key)
gtk_widget_remove_accelerator(GTK_WIDGET(menu), accel, key, mods);
g_free(_shortcut);
_shortcut = NULL;
}
if (shortcut)
{
_shortcut = g_strdup(shortcut);
gt_shortcut_parse(_shortcut, &key, &mods);
if (key)
gtk_widget_add_accelerator(GTK_WIDGET(menu),"activate",accel,key,mods,(GtkAccelFlags)0);
}
update();
}
gMainWindow *gMenu::window()
{
if (!pr)
return NULL;
if (top_level)
return (gMainWindow *)pr;
return ((gMenu *)pr)->window();
}
void gMenu::setName(char *name)
{
if (_name) g_free(_name);
_name = NULL;
if (name) _name = g_strdup(name);
}
void gMenu::hideSeparators()
{
gMenu *ch;
gMenu *last_ch;
bool is_sep;
bool last_sep;
GList *item;
if (!child)
return;
last_sep = true;
last_ch = 0;
item = g_list_first(menus);
while (item)
{
ch = (gMenu*)item->data;
if (ch->pr == this)
{
is_sep = ch->style() == SEPARATOR;
if (is_sep)
{
if (last_sep)
{
ch->hide();
}
else
{
ch->show();
last_sep = true;
last_ch = ch;
}
}
else
{
if (ch->isVisible())
last_sep = false;
}
}
item = g_list_next(item);
}
if (last_sep && last_ch)
last_ch->hide();
}