#include #include #include #include #include #include "ba0102.h" /////////////////////////////////////////////////////////// // TMenu_tree /////////////////////////////////////////////////////////// const TSubmenu& TMenu_tree::curr_submenu() const { CHECKS(_submenu, "NULL submenu ", (const char*)_curr_id); return *_submenu; } const TMenuitem& TMenu_tree::curr_item() const { const TSubmenu& sm = curr_submenu(); if (_menuitem < 0 || _menuitem >= sm.items()) { NFCHECK("Invalid submenu item %d in %s", _menuitem, (const char*)sm.name()); return sm.item(0); } return sm.item(_menuitem); } struct TFind_node_data { TString _id; size_t _count; }; struct TFind_string_data { TString _str; TAssoc_array* _ignore_list; }; HIDDEN bool find_string_callback(TTree& tree, void* jolly, word flags) { if (flags == SCAN_PRE_ORDER) { TMenu_tree& mt = (TMenu_tree&)tree; const TSubmenu& sm = mt.curr_submenu(); if (sm.disabled()) return FALSE; TFind_string_data& data = *(TFind_string_data*)jolly; if (data._ignore_list->is_key(sm.name())) return FALSE; TString desc; mt.get_description(desc); desc.upper(); if (desc.find(data._str) >= 0 || desc.match(data._str)) return TRUE; } return FALSE; } HIDDEN bool find_node_callback(TTree& tree, void* jolly, word flags) { if (flags == SCAN_PRE_ORDER) { TFind_node_data& data = *(TFind_node_data*)jolly; data._count++; TString id; tree.curr_id(id); if (id == data._id) return TRUE; } return FALSE; } HIDDEN bool find_leaf_callback(TTree& tree, void* jolly, word flags) { if (flags == SCAN_PRE_ORDER) { const TString& leaf = *(TString*)jolly; TString id; tree.curr_id(id); const int slash = id.rfind('/'); if (slash > 0) id = id.mid(slash+1); if (id == leaf) return TRUE; } return FALSE; } bool TMenu_tree::find_string(const TString& str) { TFind_string_data data; data._str = str; data._str.upper(); data._ignore_list = &_menu->search_ignore_list(); if (data._str != _menu->last_search_string()) { _menu->last_search_string() = data._str; _menu->search_ignore_list().destroy(); } goto_root(); bool ok = scan_depth_first(find_string_callback, &data, SCAN_PRE_ORDER); if (ok) _menu->search_ignore_list().add(curr_submenu().name()); else _menu->search_ignore_list().destroy(); return ok; } bool TMenu_tree::find_leaf(const TString& str) { goto_root(); bool ok = scan_depth_first(find_leaf_callback, (void *)&str, SCAN_PRE_ORDER); return ok; } void TMenu_tree::node2id(const TObject* node, TString& id) const { TString* str = (TString*)node; id = *str; } bool TMenu_tree::goto_root() { TSubmenu* sm = _menu->find(_root_id); if (sm) { _curr_id = _root_id; _curr_id << ".0"; _submenu = sm; _menuitem = 0; } return sm != NULL; } bool TMenu_tree::goto_firstson() { const TMenuitem& mi = curr_item(); if (mi.is_submenu()) { const TSubmenu* sm = _menu->find(mi.action()); if (sm && sm->items() > 0) { _curr_id << '/' << mi.action() << ".0"; _submenu = sm; _menuitem = 0; return TRUE; } } return FALSE; } bool TMenu_tree::goto_rbrother() { if (_menuitem < _submenu->items()-1) { const int dot = _curr_id.rfind('.'); _curr_id.cut(dot+1); _curr_id << (++_menuitem); return TRUE; } return FALSE; } bool TMenu_tree::goto_node(const TString &id) { const int dot = id.rfind('.'); CHECKS(dot > 0, "Invalid tree node ", (const char*)id); _menuitem = atoi(id.mid(dot+1)); _curr_id = id.left(dot); const int slash = _curr_id.rfind('/'); _curr_id = _curr_id.mid(slash+1); _submenu = _menu->find(_curr_id); _curr_id = id; return _submenu != NULL; } bool TMenu_tree::has_son() const { const TMenuitem& mi = curr_item(); return mi.is_submenu(); } bool TMenu_tree::has_rbrother() const { return _menuitem < _submenu->items()-1; } bool TMenu_tree::has_root() const { return _root_id.not_empty(); } bool TMenu_tree::has_father() const { return _curr_id.find('/') > 0; } bool TMenu_tree::has_lbrother() const { return _menuitem > 0; } bool TMenu_tree::goto_father() { const int slash = _curr_id.rfind('/'); if (slash > 0) { const TString id = _curr_id.left(slash); return goto_node(id); } return FALSE; } bool TMenu_tree::goto_lbrother() { if (_menuitem > 0) { const int dot = _curr_id.rfind('.'); _curr_id.cut(dot+1); _curr_id << (--_menuitem); return TRUE; } return FALSE; } TObject* TMenu_tree::curr_node() const { return &((TMenu_tree*)this)->_curr_id; } bool TMenu_tree::get_description(TString& desc) const { const TMenuitem& mi = curr_item(); desc = mi.caption(); return desc.not_empty(); } TImage* TMenu_tree::image(bool selected) const { const TMenuitem& mi = curr_item(); if (mi.disabled()) return get_res_image(BMP_STOP); return TTree::image(selected); } long TMenu_tree::find_node(const TString& id) { TFind_node_data data; data._id = id; data._count = 0; goto_root(); scan_depth_first(find_node_callback, &data, SCAN_PRE_ORDER | SCAN_IGNORING_UNEXPANDED); return data._count; } TMenu_tree::TMenu_tree(TMenu& menu) : _menu(&menu), _curr_id(128, '/') { _root_id = _menu->current().name(); goto_root(); } /////////////////////////////////////////////////////////// // Utility /////////////////////////////////////////////////////////// void synchronize_tree_field(TTree_field& tf) { TMenu_tree& mt = *(TMenu_tree*)tf.tree(); tf.select_current(); TString id; mt.curr_id(id); // Memorizza nodo corrente mt.shrink_all(); mt.goto_node(id); do { mt.expand(); } while (mt.goto_father()); mt.goto_node(id); tf.set_tree(&mt); // Azzera origine TField_window& win = tf.win(); win.force_update(); } bool can_be_transparent(const TImage& i) { const int w = i.width()-1; const int h = i.height()-1; const COLOR col = i.get_pixel(0,0); if (i.get_pixel(w,0) != col) return FALSE; if (i.get_pixel(w,h) != col) return FALSE; if (i.get_pixel(0,h) != col) return FALSE; return TRUE; } /////////////////////////////////////////////////////////// // TMenulist_field /////////////////////////////////////////////////////////// class TMenulist_images : public TCache { WINDOW _win; int _max_side; protected: TObject* key2obj(const char* key); public: void set_owner_info(WINDOW win, int max_side); TImage* image(const char* filename); TMenulist_images() : TCache(17), _win(NULL_WIN), _max_side(0) { } }; inline int fast_hypot(int x, int y) { // loop unrolled #define TEST(s, h, i) { const int k = h+i; if (k*k <= s) h = k; } const int s = x*x + y*y; int h = 0; TEST(s, h, 512); TEST(s, h, 256); TEST(s, h, 128); TEST(s, h, 64); TEST(s, h, 32); TEST(s, h, 16); TEST(s, h, 8); TEST(s, h, 4); TEST(s, h, 2); TEST(s, h, 1); return h; } void TMenulist_images::set_owner_info(WINDOW win, int max_side) { _win = win; _max_side = max_side; } TObject* TMenulist_images::key2obj(const char* key) { TImage* img = NULL; TFilename name; const char* ext[3] = { "jpg", "gif", "bmp" }; for (int i = 0; i < 3; i++) { name = key; name << '.' << ext[i]; name.custom_path(); if (name.exist()) break; } if (name.exist()) { TWait_cursor hourglass; TImage image(name); if (can_be_transparent(image)) image.convert_transparent_color(NORMAL_BACK_COLOR); RCT rct; xvt_vobj_get_client_rect(_win, &rct); const double max_img = (double)_max_side; const double max_lgo = rct.right - _max_side; if (strcmp(key,"logo") != 0) { const double ratiox = max_img / image.width(); const double ratioy = max_img / image.height(); const double ratio = min(ratiox, ratioy); const int maxx = int(ratio * image.width()); const int maxy = int(ratio * image.height()); img = new TImage(image, maxx, maxy); //Sfumatura costante sui lati sx e up const double radius = min(maxx,maxy)/6.0; for (int y = 0; y < maxy; y++) { for (int x = 0; x < maxx; x++) { if (x <= radius || y <= radius) { double perc = 1.0; if (x <= radius && y <= radius) { const int r = fast_hypot(int(radius-x),int(radius-y)); if (r <= radius) perc = 1.0-r/radius; else perc = 0; } else perc = min(x,y)/radius; COLOR col = img->get_pixel(x, y); COLOR bri = blend_colors(col, NORMAL_BACK_COLOR, perc); img->set_pixel(x, y, bri); } } } } else //caso particolare del logo { const double ratio = max_lgo / image.width(); const int maxx = int(ratio * image.width()); const int maxy = int(ratio * image.height()); img = new TImage(image, maxx, maxy); } } return img; } TImage* TMenulist_images::image(const char* name) { TObject* obj = objptr(name); if (obj == NULL && xvt_str_compare_ignoring_case(name, "ba00") != 0 && xvt_str_compare_ignoring_case(name, "logo") != 0) obj = objptr("ba00"); return (TImage*)obj; } class TMenulist_window : public TField_window { private: TMenu_tree* _tree; size_t MENU_COLS, MENU_ROWS; TString _curr_node; bool _can_go_back; TMenulist_images _images; TString _image_name; int _selected; TPointer_array _sorted; clock_t _last_update; protected: virtual void update(); virtual void handler(WINDOW win, EVENT* ep); virtual bool on_key(KEY k); virtual void on_idle(); void synchronize_buddy_tree() const; void draw_item(int i); void draw_menu_caption(COLOR rgb); void click_on(int index); void select(int s, int direction); public: void curr_item(TToken_string& id) const; void set_menu(TMenu_tree& mt); TMenulist_window(int x, int y, int dx, int dy, WINDOW parent, TMenulist_field* owner); virtual ~TMenulist_window(); }; void TMenulist_window::draw_item(int i) { if (i < 0 && i >= _sorted.items()) return; // Scarta elementi non validi RCT rct; xvt_vobj_get_client_rect(win(), &rct); const int width = rct.right - rct.left; const int height = rct.bottom - rct.top; xvt_set_font(win(), NULL, 0, 0); // Set default font set_opaque_text(TRUE); const TMenuitem& item = (const TMenuitem&)_sorted[i]; if (i == _selected && item.enabled()) { //testo nero su sfondo con colore del focus se la voce e' selezionata set_color(item.color(), FOCUS_BACK_COLOR); } else { set_color(item.color(), NORMAL_BACK_COLOR); //testo nero su sfondo trasparente x voci non selezionate } const int row = i / MENU_COLS; const int col = i % MENU_COLS; const int left = col * width / MENU_COLS; const int right = (col+1) * width / MENU_COLS; const int top = row * height / MENU_ROWS; // const int bottom = (row+1) * height / MENU_ROWS; const int maxchars = (right-left)/CHARX - 1; const int cx = (left+right)/2; // const int cy = (top+bottom)/2; verificare const int ico = item.enabled() ? item.icon() : 0; const int ix = cx-16; const int iy = top+2; if (item.is_submenu()) { xvt_dwin_draw_icon(win(), ix, iy, 10202); if ( ico > 0) xvt_dwin_draw_icon(win(), ix, iy+4, ico); } else { xvt_dwin_draw_icon(win(), ix, iy, ico > 0 ? ico : ICON_RSRC); } TString str = item.caption(); if (i == 0 && _can_go_back) str = "(..)"; TParagraph_string para(str, maxchars); int y = iy + 32 + CHARY-2; FOR_EACH_TOKEN(para, line) { const int ll = xvt_dwin_get_text_width(win(), line, -1); const int x = cx - ll/2; xvt_dwin_draw_text(win(), x, y, line, -1); y += CHARY-2; } if (item.disabled()) xvt_dwin_draw_icon(win(), ix+4, iy+4, 10203); // Stop icon } //scrive la voce di menu corrente a video void TMenulist_window::draw_menu_caption(COLOR rgb) { const char* caption = ""; if (_sorted.items() > 1) { const TMenuitem& mi = (const TMenuitem&)_sorted[1]; const TSubmenu& sm = mi.submenu(); caption = sm.caption(); } else { const TMenuitem& mi = (const TMenuitem&)_sorted[0]; caption = mi.caption(); } RCT rct; xvt_vobj_get_client_rect(win(), &rct); const int x = rct.left+2; int y = rct.bottom-CHARY; if (ADVANCED_GRAPHICS) { const TImage* logo = _images.image("logo"); if (logo != NULL) y -= logo->height() + ROWY; //+ROWY per staccare scritta dal logo } XVT_FNTID font_menu = xvt_font_create(); xvt_font_copy(font_menu, xvt_default_font(true), XVT_FA_ALL); const int size = xvt_font_get_size(font_menu); xvt_font_set_size(font_menu, 130 * size / 100); //altezza font il 30% maggiore di quello di menu xvt_dwin_set_font(win(), font_menu); set_color(rgb, NORMAL_BACK_COLOR); xvt_dwin_draw_text(win(), x, y, caption, -1); xvt_font_destroy(font_menu); statbar_set_title(TASK_WIN, caption); } void TMenulist_window::update() { const bool db = _tree != NULL && ADVANCED_GRAPHICS; TImage* img = db ? _images.image(_image_name) : NULL; // Delay time before clearing TField_window::update(); if (_tree == NULL) return; // Nothing to draw if (img != NULL) { RCT rct; xvt_vobj_get_client_rect(win(), &rct); const int x = (rct.right - img->width()); const int y = (rct.bottom - img->height()); img->draw(win(), x, y); } TImage* logo = db ? _images.image("logo") : NULL; //logo del programma if (logo != NULL) //disegna il logo { RCT rct; xvt_vobj_get_client_rect(win(), &rct); const int x = rct.left; const int y = rct.bottom - logo->height(); logo->draw(win(), x, y); } for (int i = 0; i < _sorted.items(); i++) draw_item(i); if (ADVANCED_GRAPHICS) _last_update = clock(); else draw_menu_caption(NORMAL_COLOR); } void TMenulist_window::on_idle() { if (ADVANCED_GRAPHICS) { #ifdef LINUX const clock_t max_clock = (3*CLOCKS_PER_SEC) / 10; #else const clock_t max_clock = 3*CLOCKS_PER_SEC; #endif const clock_t elapsed = clock() - _last_update; if (elapsed <= max_clock) { const double perc = double(elapsed) / double(max_clock); const COLOR rgb = blend_colors(NORMAL_COLOR, NORMAL_BACK_COLOR, perc); draw_menu_caption(rgb); } } } void TMenulist_window::click_on(int index) { if (index >= 0 && index < _sorted.items()) { const TMenuitem& mi = (const TMenuitem&)_sorted[index]; if (mi.enabled()) { if (xvt_vobj_get_attr(NULL_WIN, ATTR_SPEECH_MODE) & (1<<7)) { const TString& str = mi.caption(); if (str.find("..") < 0) xvt_dm_post_speech(str, 7, TRUE); } if (mi.is_submenu()) { if (index == 0 && _can_go_back) // Sł di un livello { _tree->goto_node(_curr_node); _tree->goto_father(); set_menu(*_tree); } else // Gił di un livello { if (mi.perform()) // Eventuale richiesta ditta { _tree->goto_node(_curr_node); const TSubmenu& mnu = _tree->curr_submenu(); for (int i = 0; i < mnu.items(); i++) { const TMenuitem& ti = mnu[i]; if (ti.action() == mi.action()) { _tree->goto_firstson(); set_menu(*_tree); synchronize_buddy_tree(); break; } _tree->goto_rbrother(); } } } } else { mi.perform(); } set_focus(); } else xvt_sys_beep(1); } } void TMenulist_window::handler(WINDOW win, EVENT* ep) { switch (ep->type) { case E_MOUSE_DOWN: { RCT rct; xvt_vobj_get_client_rect(win, &rct); const int row = ep->v.mouse.where.v * MENU_ROWS / rct.bottom; const int col = ep->v.mouse.where.h * MENU_COLS / rct.right; const int index = row * MENU_COLS + col; if (ep->v.mouse.button > 0) // Tasto destro { if (index < _sorted.items()) { const TMenuitem& mi = (const TMenuitem&)_sorted[index]; message_box(mi.action()); } else { if (_can_go_back) click_on(0); } } else { click_on(index); // Tasto sinistro } } break; default: break; } TField_window::handler(win, ep); } void TMenulist_window::select(int s, int direction) { const int old_selection = _selected; if (s < 0) s = 0; if (s >= _sorted.items()) s = _sorted.last(); _selected = s; const TMenuitem& mi = (const TMenuitem&)_sorted[_selected]; if (!mi.enabled()) { for (_selected += direction; ; _selected += direction) { if (_selected < 0) _selected = _sorted.last(); if (_selected >= _sorted.items()) _selected = 0; if (_selected == s) // Ho rifatto l'intero giro! break; const TMenuitem& item = (const TMenuitem&)_sorted[_selected]; if (item.enabled()) break; // Ho trovato un elemento abilitato! } } draw_item(old_selection); draw_item(_selected); } bool TMenulist_window::on_key(KEY k) { switch (k) { case K_ESC: case K_BACKSPACE: if (_tree != NULL && _can_go_back) // Sł di un livello click_on(0); break; case K_ENTER: case K_SPACE: click_on(_selected); break; case K_HOME: select(0, +1); break; case K_UP: case K_PREV: select(_selected - MENU_COLS, -1); break; case K_DOWN: case K_NEXT: select(_selected + MENU_COLS, +1); break; case K_LEFT: case K_BTAB: select(_selected-1, -1); break; case K_RIGHT: case K_TAB: select(_selected+1, +1); break; case K_END: select(_sorted.last(), -1); break; default: break; } return TRUE; } void TMenulist_window::synchronize_buddy_tree() const { TMask& m = owner().mask(); for (int i = 0; i < m.fields(); i++) { TMask_field& mf = m.fld(i); if (mf.is_kind_of(CLASS_TREE_FIELD)) { TTree_field& tf = (TTree_field&)mf; synchronize_tree_field(tf); break; } } } void TMenulist_window::curr_item(TToken_string& id) const { if (_selected >= 0 && _selected < _sorted.items()) { const TMenuitem& item = (const TMenuitem&)_sorted[_selected]; const TSubmenu& sm = item.submenu(); const int index = sm.find(item); id = item.caption(); id.add(sm.name()); id << '.' << index; } } void TMenulist_window::set_menu(TMenu_tree& tree) { _tree = &tree; tree.curr_id(_curr_node); const int dot = _curr_node.rfind('.')+1; int sel = -1; if (dot > 0) { sel = atoi(_curr_node.mid(dot)); _curr_node.overwrite("0", dot); } _sorted.destroy(); _can_go_back = tree.goto_father(); if (_can_go_back) { _sorted.add(tree.curr_item()); tree.goto_node(_curr_node); } int folders = _sorted.items(); // Lista riordinata dei menu items const TSubmenu& mnu = tree.curr_submenu(); for (int i = 0; i < mnu.items(); i++) { const TMenuitem& item = mnu[i]; if (item.is_submenu()) _sorted.insert(item, folders++); else _sorted.add(item); } TString80 sel_act; if (sel >= 0 && sel < mnu.items()) sel_act= mnu[sel].action(); for (_selected = _sorted.last(); _selected > 0; _selected--) { const TMenuitem& sm = (const TMenuitem&)_sorted[_selected]; if (sm.enabled() && sm.action() == sel_act) break; } _image_name = mnu.picture(); force_update(); } TMenulist_window::TMenulist_window(int x, int y, int dx, int dy, WINDOW parent, TMenulist_field* owner) : TField_window(x, y, dx, dy, parent, owner), _tree(NULL), MENU_COLS(4), MENU_ROWS(5) { set_scroll_max(0, 0); // Get rid of that useless scrollbars RCT rct; xvt_vobj_get_client_rect(win(), &rct); const size_t rh = 32 + 3 * CHARY; const size_t mr = rct.bottom / rh; if (mr > MENU_ROWS) MENU_ROWS = mr; int ms = rct.bottom - 3 * rh; _images.set_owner_info(win(), ms); } TMenulist_window::~TMenulist_window() { } TField_window* TMenulist_field::create_window(int x, int y, int dx, int dy, WINDOW parent) { return new TMenulist_window(x, y, dx, dy, parent, this); } void TMenulist_field::create(short dlg, int x, int y, int dx, int dy) { _dlg = dlg; _win = create_window(x, y, dx, dy, mask().win()); } void TMenulist_field::set_menu(TMenu_tree& mt) { TMenulist_window& w = (TMenulist_window&)win(); w.set_menu(mt); } void TMenulist_field::curr_item(TToken_string& id) const { TMenulist_window& w = (TMenulist_window&)win(); w.curr_item(id); }