DRC | 2ff39b8 | 2011-07-28 08:38:59 +0000 | [diff] [blame^] | 1 | // |
| 2 | // "$Id: Fl_Menu_.cxx 7903 2010-11-28 21:06:39Z matt $" |
| 3 | // |
| 4 | // Common menu code for the Fast Light Tool Kit (FLTK). |
| 5 | // |
| 6 | // Copyright 1998-2010 by Bill Spitzak and others. |
| 7 | // |
| 8 | // This library is free software; you can redistribute it and/or |
| 9 | // modify it under the terms of the GNU Library General Public |
| 10 | // License as published by the Free Software Foundation; either |
| 11 | // version 2 of the License, or (at your option) any later version. |
| 12 | // |
| 13 | // This library is distributed in the hope that it will be useful, |
| 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | // Library General Public License for more details. |
| 17 | // |
| 18 | // You should have received a copy of the GNU Library General Public |
| 19 | // License along with this library; if not, write to the Free Software |
| 20 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| 21 | // USA. |
| 22 | // |
| 23 | // Please report all bugs and problems on the following page: |
| 24 | // |
| 25 | // http://www.fltk.org/str.php |
| 26 | // |
| 27 | // This is a base class for all items that have a menu: |
| 28 | // Fl_Menu_Bar, Fl_Menu_Button, Fl_Choice |
| 29 | // This provides storage for a menu item, functions to add/modify/delete |
| 30 | // items, and a call for when the user picks a menu item. |
| 31 | |
| 32 | // More code in Fl_Menu_add.cxx |
| 33 | |
| 34 | #include <FL/Fl.H> |
| 35 | #include <FL/Fl_Menu_.H> |
| 36 | #include "flstring.h" |
| 37 | #include <stdio.h> |
| 38 | #include <stdlib.h> |
| 39 | |
| 40 | #define SAFE_STRCAT(s) { len += strlen(s); if ( len >= namelen ) { *name='\0'; return(-2); } else strcat(name,(s)); } |
| 41 | |
| 42 | /** Get the menu 'pathname' for the specified menuitem. |
| 43 | |
| 44 | If finditem==NULL, mvalue() is used (the most recently picked menuitem). |
| 45 | |
| 46 | \b Example: |
| 47 | \code |
| 48 | Fl_Menu_Bar *menubar = 0; |
| 49 | void my_menu_callback(Fl_Widget*,void*) { |
| 50 | char name[80]; |
| 51 | if ( menubar->item_pathname(name, sizeof(name)-1) == 0 ) { // recently picked item |
| 52 | if ( strcmp(name, "File/&Open") == 0 ) { .. } // open invoked |
| 53 | if ( strcmp(name, "File/&Save") == 0 ) { .. } // save invoked |
| 54 | if ( strcmp(name, "Edit/&Copy") == 0 ) { .. } // copy invoked |
| 55 | } |
| 56 | } |
| 57 | int main() { |
| 58 | [..] |
| 59 | menubar = new Fl_Menu_Bar(..); |
| 60 | menubar->add("File/&Open", 0, my_menu_callback); |
| 61 | menubar->add("File/&Save", 0, my_menu_callback); |
| 62 | menubar->add("Edit/&Copy", 0, my_menu_callback); |
| 63 | [..] |
| 64 | } |
| 65 | \endcode |
| 66 | |
| 67 | \returns |
| 68 | - 0 : OK (name has menuitem's pathname) |
| 69 | - -1 : item not found (name="") |
| 70 | - -2 : 'name' not large enough (name="") |
| 71 | \see find_item() |
| 72 | */ |
| 73 | int Fl_Menu_::item_pathname(char *name, int namelen, const Fl_Menu_Item *finditem) const { |
| 74 | int len = 0; |
| 75 | finditem = finditem ? finditem : mvalue(); |
| 76 | name[0] = '\0'; |
| 77 | for ( int t=0; t<size(); t++ ) { |
| 78 | const Fl_Menu_Item *m = &(menu()[t]); |
| 79 | if ( m->submenu() ) { // submenu? descend |
| 80 | if (*name) SAFE_STRCAT("/"); |
| 81 | if (m->label()) SAFE_STRCAT(m->label()); |
| 82 | if ( m == finditem ) return(0); // found? done. |
| 83 | } else { |
| 84 | if (m->label()) { // menu item? |
| 85 | if ( m == finditem ) { // found? tack on itemname, done. |
| 86 | SAFE_STRCAT("/"); |
| 87 | SAFE_STRCAT(m->label()); |
| 88 | return(0); |
| 89 | } |
| 90 | } else { // end of submenu? pop |
| 91 | char *ss = strrchr(name, '/'); |
| 92 | if ( ss ) { *ss = 0; len = strlen(name); } // "File/Edit" -> "File" |
| 93 | else { name[0] = '\0'; len = 0; } // "File" -> "" |
| 94 | continue; |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | *name = '\0'; |
| 99 | return(-1); // item not found |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | Find the menu item for a given menu \p pathname, such as "Edit/Copy". |
| 104 | |
| 105 | This method finds a menu item in the menu array, also traversing submenus, but |
| 106 | not submenu pointers. |
| 107 | |
| 108 | To get the menu item's index, use find_index(const char*) |
| 109 | |
| 110 | \b Example: |
| 111 | \code |
| 112 | Fl_Menu_Bar *menubar = new Fl_Menu_Bar(..); |
| 113 | menubar->add("File/&Open"); |
| 114 | menubar->add("File/&Save"); |
| 115 | menubar->add("Edit/&Copy"); |
| 116 | // [..] |
| 117 | Fl_Menu_Item *item; |
| 118 | if ( ( item = (Fl_Menu_Item*)menubar->find_item("File/&Open") ) != NULL ) { |
| 119 | item->labelcolor(FL_RED); |
| 120 | } |
| 121 | if ( ( item = (Fl_Menu_Item*)menubar->find_item("Edit/&Copy") ) != NULL ) { |
| 122 | item->labelcolor(FL_GREEN); |
| 123 | } |
| 124 | \endcode |
| 125 | |
| 126 | \param pathname The path and name of the menu item |
| 127 | \returns The item found, or NULL if not found |
| 128 | \see find_index(const char*), find_item(Fl_Callback*), item_pathname() |
| 129 | */ |
| 130 | const Fl_Menu_Item * Fl_Menu_::find_item(const char *pathname) { |
| 131 | int i = find_index(pathname); |
| 132 | return( (i==-1) ? 0 : (const Fl_Menu_Item*)(menu_+i)); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | Find the index the menu array for given \p item. |
| 137 | |
| 138 | A way to convert a menu item pointer into an index. |
| 139 | |
| 140 | Current implementation is fast and not expensive. |
| 141 | |
| 142 | \code |
| 143 | // Convert an index-to-item |
| 144 | int index = 12; |
| 145 | const Fl_Menu_Item *item = mymenu->menu() + index; |
| 146 | |
| 147 | // Convert an item-to-index |
| 148 | int index = mymenu->find_index(item); |
| 149 | if ( index == -1 ) { ..error.. } |
| 150 | \endcode |
| 151 | |
| 152 | \param item The *item to be found |
| 153 | \returns The index of the item, or -1 if not found. |
| 154 | \see menu() |
| 155 | */ |
| 156 | int Fl_Menu_::find_index(const Fl_Menu_Item *item) const { |
| 157 | Fl_Menu_Item *max = menu_+size(); |
| 158 | if (item<menu_ || item>=max) return(-1); |
| 159 | return(item-menu_); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | Find the index into the menu array for a given callback \p cb. |
| 164 | |
| 165 | This method finds a menu item's index position, also traversing submenus, but |
| 166 | not submenu pointers. This is useful if an application uses internationalisation |
| 167 | and a menu item can not be found using its label. This search is also much faster. |
| 168 | |
| 169 | \param cb Find the first item with this callback |
| 170 | \returns The index of the item with the specific callback, or -1 if not found |
| 171 | \see find_index(const char*) |
| 172 | */ |
| 173 | int Fl_Menu_::find_index(Fl_Callback *cb) const { |
| 174 | for ( int t=0; t < size(); t++ ) |
| 175 | if (menu_[t].callback_==cb) |
| 176 | return(t); |
| 177 | return(-1); |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | Find the menu item index for a given menu \p pathname, such as "Edit/Copy". |
| 182 | |
| 183 | This method finds a menu item's index position for the given menu pathname, |
| 184 | also traversing submenus, but not submenu pointers. |
| 185 | |
| 186 | To get the menu item pointer for a pathname, use find_item() |
| 187 | |
| 188 | \param pathname The path and name of the menu item index to find |
| 189 | \returns The index of the matching item, or -1 if not found. |
| 190 | \see item_pathname() |
| 191 | |
| 192 | */ |
| 193 | int Fl_Menu_::find_index(const char *pathname) const { |
| 194 | char menupath[1024] = ""; // File/Export |
| 195 | for ( int t=0; t < size(); t++ ) { |
| 196 | Fl_Menu_Item *m = menu_ + t; |
| 197 | if (m->flags&FL_SUBMENU) { |
| 198 | // IT'S A SUBMENU |
| 199 | // we do not support searches through FL_SUBMENU_POINTER links |
| 200 | if (menupath[0]) strlcat(menupath, "/", sizeof(menupath)); |
| 201 | strlcat(menupath, m->label(), sizeof(menupath)); |
| 202 | if (!strcmp(menupath, pathname)) return(t); |
| 203 | } else { |
| 204 | if (!m->label()) { |
| 205 | // END OF SUBMENU? Pop back one level. |
| 206 | char *ss = strrchr(menupath, '/'); |
| 207 | if ( ss ) *ss = 0; |
| 208 | else menupath[0] = '\0'; |
| 209 | continue; |
| 210 | } |
| 211 | // IT'S A MENU ITEM |
| 212 | char itempath[1024]; // eg. Edit/Copy |
| 213 | strcpy(itempath, menupath); |
| 214 | if (itempath[0]) strlcat(itempath, "/", sizeof(itempath)); |
| 215 | strlcat(itempath, m->label(), sizeof(itempath)); |
| 216 | if (!strcmp(itempath, pathname)) return(t); |
| 217 | } |
| 218 | } |
| 219 | return(-1); |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | Find the menu item for the given callback \p cb. |
| 224 | |
| 225 | This method finds a menu item in a menu array, also traversing submenus, but |
| 226 | not submenu pointers. This is useful if an application uses |
| 227 | internationalisation and a menu item can not be found using its label. This |
| 228 | search is also much faster. |
| 229 | |
| 230 | \param cb find the first item with this callback |
| 231 | \returns The item found, or NULL if not found |
| 232 | \see find_item(const char*) |
| 233 | */ |
| 234 | const Fl_Menu_Item * Fl_Menu_::find_item(Fl_Callback *cb) { |
| 235 | for ( int t=0; t < size(); t++ ) { |
| 236 | const Fl_Menu_Item *m = menu_ + t; |
| 237 | if (m->callback_==cb) { |
| 238 | return m; |
| 239 | } |
| 240 | } |
| 241 | return (const Fl_Menu_Item *)0; |
| 242 | } |
| 243 | |
| 244 | /** |
| 245 | The value is the index into menu() of the last item chosen by |
| 246 | the user. It is zero initially. You can set it as an integer, or set |
| 247 | it with a pointer to a menu item. The set routines return non-zero if |
| 248 | the new value is different than the old one. |
| 249 | */ |
| 250 | int Fl_Menu_::value(const Fl_Menu_Item* m) { |
| 251 | clear_changed(); |
| 252 | if (value_ != m) {value_ = m; return 1;} |
| 253 | return 0; |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | When user picks a menu item, call this. It will do the callback. |
| 258 | Unfortunately this also casts away const for the checkboxes, but this |
| 259 | was necessary so non-checkbox menus can really be declared const... |
| 260 | */ |
| 261 | const Fl_Menu_Item* Fl_Menu_::picked(const Fl_Menu_Item* v) { |
| 262 | if (v) { |
| 263 | if (v->radio()) { |
| 264 | if (!v->value()) { // they are turning on a radio item |
| 265 | set_changed(); |
| 266 | ((Fl_Menu_Item*)v)->setonly(); |
| 267 | } |
| 268 | redraw(); |
| 269 | } else if (v->flags & FL_MENU_TOGGLE) { |
| 270 | set_changed(); |
| 271 | ((Fl_Menu_Item*)v)->flags ^= FL_MENU_VALUE; |
| 272 | redraw(); |
| 273 | } else if (v != value_) { // normal item |
| 274 | set_changed(); |
| 275 | } |
| 276 | value_ = v; |
| 277 | if (when()&(FL_WHEN_CHANGED|FL_WHEN_RELEASE)) { |
| 278 | if (changed() || when()&FL_WHEN_NOT_CHANGED) { |
| 279 | if (value_ && value_->callback_) value_->do_callback((Fl_Widget*)this); |
| 280 | else do_callback(); |
| 281 | } |
| 282 | } |
| 283 | } |
| 284 | return v; |
| 285 | } |
| 286 | |
| 287 | /** Turns the radio item "on" for the menu item and turns off adjacent radio items set. */ |
| 288 | void Fl_Menu_Item::setonly() { |
| 289 | flags |= FL_MENU_RADIO | FL_MENU_VALUE; |
| 290 | Fl_Menu_Item* j; |
| 291 | for (j = this; ; ) { // go down |
| 292 | if (j->flags & FL_MENU_DIVIDER) break; // stop on divider lines |
| 293 | j++; |
| 294 | if (!j->text || !j->radio()) break; // stop after group |
| 295 | j->clear(); |
| 296 | } |
| 297 | for (j = this-1; ; j--) { // go up |
| 298 | if (!j->text || (j->flags&FL_MENU_DIVIDER) || !j->radio()) break; |
| 299 | j->clear(); |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | Creates a new Fl_Menu_ widget using the given position, size, |
| 305 | and label string. menu() is initialized to null. |
| 306 | */ |
| 307 | Fl_Menu_::Fl_Menu_(int X,int Y,int W,int H,const char* l) |
| 308 | : Fl_Widget(X,Y,W,H,l) { |
| 309 | set_flag(SHORTCUT_LABEL); |
| 310 | box(FL_UP_BOX); |
| 311 | when(FL_WHEN_RELEASE_ALWAYS); |
| 312 | value_ = menu_ = 0; |
| 313 | alloc = 0; |
| 314 | selection_color(FL_SELECTION_COLOR); |
| 315 | textfont(FL_HELVETICA); |
| 316 | textsize(FL_NORMAL_SIZE); |
| 317 | textcolor(FL_FOREGROUND_COLOR); |
| 318 | down_box(FL_NO_BOX); |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | This returns the number of Fl_Menu_Item structures that make up the |
| 323 | menu, correctly counting submenus. This includes the "terminator" |
| 324 | item at the end. To copy a menu array you need to copy |
| 325 | size()*sizeof(Fl_Menu_Item) bytes. If the menu is |
| 326 | NULL this returns zero (an empty menu will return 1). |
| 327 | */ |
| 328 | int Fl_Menu_::size() const { |
| 329 | if (!menu_) return 0; |
| 330 | return menu_->size(); |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | Sets the menu array pointer directly. If the old menu is private it is |
| 335 | deleted. NULL is allowed and acts the same as a zero-length |
| 336 | menu. If you try to modify the array (with add(), replace(), or |
| 337 | remove()) a private copy is automatically done. |
| 338 | */ |
| 339 | void Fl_Menu_::menu(const Fl_Menu_Item* m) { |
| 340 | clear(); |
| 341 | value_ = menu_ = (Fl_Menu_Item*)m; |
| 342 | } |
| 343 | |
| 344 | // this version is ok with new Fl_Menu_add code with fl_menu_array_owner: |
| 345 | |
| 346 | /** |
| 347 | Sets the menu array pointer with a copy of m that will be automatically deleted. |
| 348 | If userdata \p ud is not NULL, then all user data pointers are changed in the menus as well. |
| 349 | See void Fl_Menu_::menu(const Fl_Menu_Item* m). |
| 350 | */ |
| 351 | void Fl_Menu_::copy(const Fl_Menu_Item* m, void* ud) { |
| 352 | int n = m->size(); |
| 353 | Fl_Menu_Item* newMenu = new Fl_Menu_Item[n]; |
| 354 | memcpy(newMenu, m, n*sizeof(Fl_Menu_Item)); |
| 355 | menu(newMenu); |
| 356 | alloc = 1; // make destructor free array, but not strings |
| 357 | // for convenience, provide way to change all the user data pointers: |
| 358 | if (ud) for (; n--;) { |
| 359 | if (newMenu->callback_) newMenu->user_data_ = ud; |
| 360 | newMenu++; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | Fl_Menu_::~Fl_Menu_() { |
| 365 | clear(); |
| 366 | } |
| 367 | |
| 368 | // Fl_Menu::add() uses this to indicate the owner of the dynamically- |
| 369 | // expanding array. We must not free this array: |
| 370 | Fl_Menu_* fl_menu_array_owner = 0; |
| 371 | |
| 372 | /** |
| 373 | Same as menu(NULL), set the array pointer to null, indicating |
| 374 | a zero-length menu. |
| 375 | |
| 376 | Menus must not be cleared during a callback to the same menu. |
| 377 | */ |
| 378 | void Fl_Menu_::clear() { |
| 379 | if (alloc) { |
| 380 | if (alloc>1) for (int i = size(); i--;) |
| 381 | if (menu_[i].text) free((void*)menu_[i].text); |
| 382 | if (this == fl_menu_array_owner) |
| 383 | fl_menu_array_owner = 0; |
| 384 | else |
| 385 | delete[] menu_; |
| 386 | menu_ = 0; |
| 387 | value_ = 0; |
| 388 | alloc = 0; |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | /** |
| 393 | Clears the specified submenu pointed to by \p index of all menu items. |
| 394 | |
| 395 | This method is useful for clearing a submenu so that it can be |
| 396 | re-populated with new items. Example: a "File/Recent Files/..." submenu |
| 397 | that shows the last few files that have been opened. |
| 398 | |
| 399 | The specified \p index must point to a submenu. |
| 400 | |
| 401 | The submenu is cleared with remove(). |
| 402 | If the menu array was directly set with menu(x), then copy() |
| 403 | is done to make a private array. |
| 404 | |
| 405 | \warning Since this method can change the internal menu array, any menu |
| 406 | item pointers or indecies the application may have cached can become |
| 407 | stale, and should be recalculated/refreshed. |
| 408 | |
| 409 | \b Example: |
| 410 | \code |
| 411 | int index = menubar->find_index("File/Recent"); // get index of "File/Recent" submenu |
| 412 | if ( index != -1 ) menubar->clear_submenu(index); // clear the submenu |
| 413 | menubar->add("File/Recent/Aaa"); |
| 414 | menubar->add("File/Recent/Bbb"); |
| 415 | [..] |
| 416 | \endcode |
| 417 | |
| 418 | \param index The index of the submenu to be cleared |
| 419 | \returns 0 on success, -1 if the index is out of range or not a submenu |
| 420 | \see remove(int) |
| 421 | */ |
| 422 | int Fl_Menu_::clear_submenu(int index) { |
| 423 | if ( index < 0 || index >= size() ) return(-1); |
| 424 | if ( ! (menu_[index].flags & FL_SUBMENU) ) return(-1); |
| 425 | ++index; // advance to first item in submenu |
| 426 | while ( index < size() ) { // keep remove()ing top item until end is reached |
| 427 | if ( menu_[index].text == 0 ) break; // end of this submenu? done |
| 428 | remove(index); // remove items/submenus |
| 429 | } |
| 430 | return(0); |
| 431 | } |
| 432 | |
| 433 | // |
| 434 | // End of "$Id: Fl_Menu_.cxx 7903 2010-11-28 21:06:39Z matt $". |
| 435 | // |