0f180e1306
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@7519 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
459 lines
16 KiB
C++
459 lines
16 KiB
C++
//
|
|
// "$Id$"
|
|
//
|
|
// Menu utilities for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 1998-2009 by Bill Spitzak and others.
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Library General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Library General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Library General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
// USA.
|
|
//
|
|
// Please report all bugs and problems on the following page:
|
|
//
|
|
// http://www.fltk.org/str.php
|
|
//
|
|
// Methods to alter the menu in an Fl_Menu_ widget.
|
|
|
|
// These are for Forms emulation and for dynamically changing the
|
|
// menus. They are in this source file so they are not linked in if
|
|
// not used, which is what will happen if the program only uses
|
|
// constant menu tables.
|
|
|
|
// Not at all guaranteed to be Forms compatible, especially with any
|
|
// string with a % sign in it!
|
|
|
|
#include <FL/Fl_Menu_.H>
|
|
#include "flstring.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
// If the array is this, we will double-reallocate as necessary:
|
|
static Fl_Menu_Item* local_array = 0;
|
|
static int local_array_alloc = 0; // number allocated
|
|
static int local_array_size = 0; // == size(local_array)
|
|
extern Fl_Menu_* fl_menu_array_owner; // in Fl_Menu_.cxx
|
|
|
|
// For historical reasons there are matching methods that work on a
|
|
// user-allocated array of Fl_Menu_Item. These methods are quite
|
|
// depreciated and should not be used. These old methods use the
|
|
// above pointers to detect if the array belongs to an Fl_Menu_
|
|
// widget, and if so it reallocates as necessary.
|
|
|
|
// Insert a single Fl_Menu_Item into an array of size at offset n,
|
|
// if this is local_array it will be reallocated if needed.
|
|
static Fl_Menu_Item* array_insert(
|
|
Fl_Menu_Item* array, // array to modify
|
|
int size, // size of array
|
|
int n, // index of new insert position
|
|
const char *text, // text of new item (copy is made)
|
|
int flags // flags for new item
|
|
) {
|
|
if (array == local_array && size >= local_array_alloc) {
|
|
local_array_alloc = 2*size;
|
|
Fl_Menu_Item* newarray = new Fl_Menu_Item[local_array_alloc];
|
|
memmove(newarray, array, size*sizeof(Fl_Menu_Item));
|
|
delete[] local_array;
|
|
local_array = array = newarray;
|
|
}
|
|
// move all the later items:
|
|
memmove(array+n+1, array+n, sizeof(Fl_Menu_Item)*(size-n));
|
|
// create the new item:
|
|
Fl_Menu_Item* m = array+n;
|
|
m->text = text ? strdup(text) : 0;
|
|
m->shortcut_ = 0;
|
|
m->callback_ = 0;
|
|
m->user_data_ = 0;
|
|
m->flags = flags;
|
|
m->labeltype_ = m->labelsize_ = m->labelcolor_ = 0;
|
|
m->labelfont_ = FL_HELVETICA;
|
|
return array;
|
|
}
|
|
|
|
// Comparison that does not care about deleted '&' signs:
|
|
static int compare(const char* a, const char* b) {
|
|
for (;;) {
|
|
int n = *a-*b;
|
|
if (n) {
|
|
if (*a == '&') a++;
|
|
else if (*b == '&') b++;
|
|
else return n;
|
|
} else if (*a) {
|
|
a++; b++;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Adds an item. The text is split at '/' characters to automatically
|
|
produce submenus (actually a totally unnecessary feature as you can
|
|
now add submenu titles directly by setting SUBMENU in the flags):
|
|
*/
|
|
int Fl_Menu_Item::add(
|
|
const char *mytext,
|
|
int sc,
|
|
Fl_Callback *cb,
|
|
void *data,
|
|
int myflags
|
|
) {
|
|
return(insert(-1,mytext,sc,cb,data,myflags)); // -1: append
|
|
}
|
|
|
|
/** Inserts an item at position \p index.
|
|
|
|
If \p index is -1, the item is added the same way as Fl_Menu_Item::add().
|
|
|
|
If 'mytext' contains any un-escaped front slashes (/), it's assumed
|
|
a menu pathname is being specified, and the value of \p index
|
|
will be ignored.
|
|
|
|
In all other aspects, the behavior of insert() is the same as add().
|
|
*/
|
|
int Fl_Menu_Item::insert(
|
|
int index,
|
|
const char *mytext,
|
|
int sc,
|
|
Fl_Callback *cb,
|
|
void *data,
|
|
int myflags
|
|
) {
|
|
Fl_Menu_Item *array = this;
|
|
Fl_Menu_Item *m = this;
|
|
const char *p;
|
|
char *q;
|
|
char buf[1024];
|
|
|
|
int msize = array==local_array ? local_array_size : array->size();
|
|
int flags1 = 0;
|
|
const char* item;
|
|
|
|
// split at slashes to make submenus:
|
|
for (;;) {
|
|
|
|
// leading slash makes us assume it is a filename:
|
|
if (*mytext == '/') {item = mytext; break;}
|
|
|
|
// leading underscore causes divider line:
|
|
if (*mytext == '_') {mytext++; flags1 = FL_MENU_DIVIDER;}
|
|
|
|
// copy to buf, changing \x to x:
|
|
q = buf;
|
|
for (p=mytext; *p && *p != '/'; *q++ = *p++) if (*p=='\\' && p[1]) p++;
|
|
*q = 0;
|
|
|
|
item = buf;
|
|
if (*p != '/') break; /* not a menu title */
|
|
index = -1; /* any submenu specified overrides insert position */
|
|
mytext = p+1; /* point at item title */
|
|
|
|
/* find a matching menu title: */
|
|
for (; m->text; m = m->next())
|
|
if (m->flags&FL_SUBMENU && !compare(item, m->text)) break;
|
|
|
|
if (!m->text) { /* create a new menu */
|
|
int n = (index==-1) ? m-array : index;
|
|
array = array_insert(array, msize, n, item, FL_SUBMENU|flags1);
|
|
msize++;
|
|
array = array_insert(array, msize, n+1, 0, 0);
|
|
msize++;
|
|
m = array+n;
|
|
}
|
|
m++; /* go into the submenu */
|
|
flags1 = 0;
|
|
}
|
|
|
|
/* find a matching menu item: */
|
|
for (; m->text; m = m->next())
|
|
if (!(m->flags&FL_SUBMENU) && !compare(m->text,item)) break;
|
|
|
|
if (!m->text) { /* add a new menu item */
|
|
int n = (index==-1) ? m-array : index;
|
|
array = array_insert(array, msize, n, item, myflags|flags1);
|
|
msize++;
|
|
if (myflags & FL_SUBMENU) { // add submenu delimiter
|
|
array = array_insert(array, msize, n+1, 0, 0);
|
|
msize++;
|
|
}
|
|
m = array+n;
|
|
}
|
|
|
|
/* fill it in */
|
|
m->shortcut_ = sc;
|
|
m->callback_ = cb;
|
|
m->user_data_ = data;
|
|
m->flags = myflags|flags1;
|
|
|
|
if (array == local_array) local_array_size = msize;
|
|
return m-array;
|
|
}
|
|
|
|
/**
|
|
Adds a new menu item.
|
|
|
|
\param[in] label The text label for the menu item.
|
|
\param[in] shortcut Optional keyboard shortcut that can be an int or string; (FL_CTRL+'a') or "^a". Default 0 if none.
|
|
\param[in] callback Optional callback invoked when user clicks the item. Default 0 if none.
|
|
\param[in] userdata Optional user data passed as an argument to the callback. Default 0 if none.
|
|
\param[in] flags Optional flags that control the type of menu item; see below. Default is 0 for none.
|
|
\returns The index into the menu() array, where the entry was added.
|
|
|
|
\par Description
|
|
If the menu array was directly set with menu(x), then copy() is done
|
|
to make a private array.
|
|
\par
|
|
Since this method can change the internal menu array, any menu item
|
|
pointers or indecies the application may have cached can become stale,
|
|
and should be recalculated/refreshed.
|
|
\par
|
|
A menu item's callback must not add() items to its parent menu during the callback.
|
|
|
|
<B>Detailed Description of Parameters</B>
|
|
\par label
|
|
The menu item's label. This option is required.
|
|
\par
|
|
The characters "&", "/", "\", and "_" are treated as special characters in the label string.
|
|
The "&" character specifies that the following character is an accelerator and will be underlined.
|
|
The "\" character is used to escape the next character in the string.
|
|
Labels starting with the "_" character cause a divider to be placed after that menu item.
|
|
\par
|
|
A label of the form "File/Quit" will create the submenu "File"
|
|
with a menu item called "Quit". The "/" character is ignored if it appears
|
|
as the first character of the label string, e.g. "/File/Quit".
|
|
\par
|
|
The label string is copied to new memory and can be freed.
|
|
The other arguments (including the shortcut) are copied into the
|
|
menu item unchanged.
|
|
\par
|
|
If an item exists already with that name then it is replaced with
|
|
this new one. Otherwise this new one is added to the end of the
|
|
correct menu or submenu. The return value is the offset into the array
|
|
that the new entry was placed at.
|
|
|
|
\par shortcut
|
|
The keyboard shortcut for this menu item.
|
|
\par
|
|
This parameter is optional, and defaults to 0 to indicate no shortcut.
|
|
\par
|
|
The shortcut can either be a raw integer value (eg. FL_CTRL+'A')
|
|
or a string (eg. "^c" or "^97").
|
|
\par
|
|
Raw integer shortcuts can be a combination of keyboard chars (eg. 'A')
|
|
and optional keyboard modifiers (see Fl::event_state(), e.g. FL_SHIFT, etc).
|
|
\par
|
|
String shortcuts can be specified in one of two ways:
|
|
\par
|
|
\verbatim
|
|
[#+^]<ascii_value> e.g. "97", "^97", "+97", "#97"
|
|
[#+^]<ascii_char> e.g. "a", "^a", "+a", "#a"
|
|
\endverbatim
|
|
\par
|
|
..where \<ascii_value\> is a decimal value representing an
|
|
ascii character (eg. 97 is the ascii code for 'a'), and the optional
|
|
prefixes enhance the value that follows. Multiple prefixes must
|
|
appear in the above order.
|
|
\par
|
|
\verbatim
|
|
# - Alt
|
|
+ - Shift
|
|
^ - Control
|
|
\endverbatim
|
|
\par
|
|
Internally, the text shortcuts are converted to integer values using
|
|
fl_old_shortcut(const char*).
|
|
|
|
\par callback
|
|
The callback to invoke when this menu item is selected.
|
|
\par
|
|
This parameter is optional, and defaults to 0 for no callback.
|
|
|
|
\par userdata
|
|
The callback's 'user data' that is passed to the callback.
|
|
\par
|
|
This parameter is optional, and defaults to 0.
|
|
|
|
\par flags
|
|
These are bit flags to define what kind of menu item this is.
|
|
\par
|
|
This parameter is optional, and defaults to 0 to define a 'regular' menu item.
|
|
\par
|
|
These flags can be 'OR'ed together:
|
|
\code
|
|
FL_MENU_INACTIVE // Deactivate menu item (gray out)
|
|
FL_MENU_TOGGLE // Item is a checkbox toggle (shows checkbox for on/off state)
|
|
FL_MENU_VALUE // The on/off state for checkbox/radio buttons (if set, state is 'on')
|
|
FL_MENU_RADIO // Item is a radio button (one checkbox of many can be on)
|
|
FL_MENU_INVISIBLE // Item will not show up (shortcut will work)
|
|
FL_SUBMENU_POINTER // Indicates user_data() is a pointer to another menu array
|
|
FL_SUBMENU // This item is a submenu to other items
|
|
FL_MENU_DIVIDER // Creates divider line below this item. Also ends a group of radio buttons.
|
|
\endcode
|
|
|
|
\todo Raw integer shortcut needs examples.
|
|
Dependent on responses to http://fltk.org/newsgroups.php?gfltk.development+v:10086 and results of STR#2344
|
|
*/
|
|
int Fl_Menu_::add(const char *label,int shortcut,Fl_Callback *callback,void *userdata,int flags) {
|
|
return(insert(-1,label,shortcut,callback,userdata,flags)); // -1: append
|
|
}
|
|
|
|
/**
|
|
Inserts a new menu item at the specified \p index position.
|
|
|
|
If \p index is -1, the menu item is appended; same behavior as add().
|
|
|
|
To properly insert a menu item, \p label must be the name of the item (eg. "Quit"),
|
|
and not a 'menu pathname' (eg. "File/Quit"). If a menu pathname is specified,
|
|
the value of \p index is \em ignored, the new item's position defined by the pathname.
|
|
|
|
For more details, see add(). Except for the \p index parameter, add()
|
|
has more detailed information on parameters and behavior, and is
|
|
functionally equivalent.
|
|
|
|
\param[in] index The menu array's index position where the new item
|
|
is inserted. If -1, behavior is the same as add().
|
|
\param[in] label The text label for the menu item. If the label
|
|
is a menu pathname, \p index is ignored, and the pathname
|
|
indicates the position of the new item.
|
|
\param[in] shortcut Optional keyboard shortcut. Can be an int (FL_CTRL+'a')
|
|
or a string ("^a"). Default is 0.
|
|
\param[in] callback Optional callback invoked when user clicks the item.
|
|
Default 0 if none.
|
|
\param[in] userdata Optional user data passed as an argument to the callback.
|
|
Default 0 if none.
|
|
\param[in] flags Optional flags that control the type of menu item;
|
|
see add() for more info. Default is 0 for none.
|
|
\returns The index into the menu() array, where the entry was added.
|
|
|
|
\see add()
|
|
|
|
*/
|
|
int Fl_Menu_::insert(
|
|
int index,
|
|
const char *label,
|
|
int shortcut,
|
|
Fl_Callback *callback,
|
|
void *userdata,
|
|
int flags
|
|
) {
|
|
// make this widget own the local array:
|
|
if (this != fl_menu_array_owner) {
|
|
if (fl_menu_array_owner) {
|
|
Fl_Menu_* o = fl_menu_array_owner;
|
|
// the previous owner get's its own correctly-sized array:
|
|
int value_offset = o->value_-local_array;
|
|
int n = local_array_size;
|
|
Fl_Menu_Item* newMenu = o->menu_ = new Fl_Menu_Item[n];
|
|
memcpy(newMenu, local_array, n*sizeof(Fl_Menu_Item));
|
|
if (o->value_) o->value_ = newMenu+value_offset;
|
|
}
|
|
if (menu_) {
|
|
// this already has a menu array, use it as the local one:
|
|
delete[] local_array;
|
|
if (!alloc) copy(menu_); // duplicate a user-provided static array
|
|
// add to the menu's current array:
|
|
local_array_alloc = local_array_size = size();
|
|
local_array = menu_;
|
|
} else {
|
|
// start with a blank array:
|
|
alloc = 2; // indicates that the strings can be freed
|
|
if (local_array) {
|
|
menu_ = local_array;
|
|
} else {
|
|
local_array_alloc = 15;
|
|
local_array = menu_ = new Fl_Menu_Item[local_array_alloc];
|
|
memset(local_array, 0, sizeof(Fl_Menu_Item) * local_array_alloc);
|
|
}
|
|
memset(menu_, 0, sizeof(Fl_Menu_Item));
|
|
local_array_size = 1;
|
|
}
|
|
fl_menu_array_owner = this;
|
|
}
|
|
int r = menu_->insert(index,label,shortcut,callback,userdata,flags);
|
|
// if it rellocated array we must fix the pointer:
|
|
int value_offset = value_-menu_;
|
|
menu_ = local_array; // in case it reallocated it
|
|
if (value_) value_ = menu_+value_offset;
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
This is a Forms (and SGI GL library) compatible add function, it
|
|
adds many menu items, with '|' separating the menu items, and tab
|
|
separating the menu item names from an optional shortcut string.
|
|
|
|
The passed string is split at any '|' characters and then
|
|
add(s,0,0,0,0) is done with each section. This is
|
|
often useful if you are just using the value, and is compatible
|
|
with Forms and other GL programs. The section strings use the
|
|
same special characters as described for the long version of add().
|
|
|
|
No items must be added to a menu during a callback to the same menu.
|
|
*/
|
|
int Fl_Menu_::add(const char *str) {
|
|
char buf[1024];
|
|
int r = 0;
|
|
while (*str) {
|
|
int sc = 0;
|
|
char *c;
|
|
for (c = buf; c < (buf + sizeof(buf) - 2) && *str && *str != '|'; str++) {
|
|
if (*str == '\t') {*c++ = 0; sc = fl_old_shortcut(str);}
|
|
else *c++ = *str;
|
|
}
|
|
*c = 0;
|
|
r = add(buf, sc, 0, 0, 0);
|
|
if (*str) str++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
Changes the text of item \p i. This is the only way to get
|
|
slash into an add()'ed menu item. If the menu array was directly set
|
|
with menu(x) then copy() is done to make a private array.
|
|
*/
|
|
void Fl_Menu_::replace(int i, const char *str) {
|
|
if (i<0 || i>=size()) return;
|
|
if (!alloc) copy(menu_);
|
|
if (alloc > 1) {
|
|
free((void *)menu_[i].text);
|
|
str = strdup(str);
|
|
}
|
|
menu_[i].text = str;
|
|
}
|
|
/**
|
|
Deletes item \p i from the menu. If the menu array was directly
|
|
set with menu(x) then copy() is done to make a private array.
|
|
|
|
No items must be removed from a menu during a callback to the same menu.
|
|
*/
|
|
void Fl_Menu_::remove(int i) {
|
|
int n = size();
|
|
if (i<0 || i>=n) return;
|
|
if (!alloc) copy(menu_);
|
|
// find the next item, skipping submenus:
|
|
Fl_Menu_Item* item = menu_+i;
|
|
const Fl_Menu_Item* next_item = item->next();
|
|
// delete the text only if all items were created with add():
|
|
if (alloc > 1) {
|
|
for (Fl_Menu_Item* m = item; m < next_item; m++)
|
|
if (m->text) free((void*)(m->text));
|
|
}
|
|
// MRS: "n" is the menu size(), which includes the trailing NULL entry...
|
|
memmove(item, next_item, (menu_+n-next_item)*sizeof(Fl_Menu_Item));
|
|
}
|
|
|
|
//
|
|
// End of "$Id$".
|
|
//
|