// ==================================================================== // // // // File : item_sel_list.cxx // // Purpose : selection lists for items (ItemSelector) // // // // // // Coded by Ralf Westram (coder@reallysoft.de) in May 2005 // // Copyright Department of Microbiology (Technical University Munich) // // // // Visit our web site at: http://www.arb-home.de/ // // // // ==================================================================== // #include "item_sel_list.h" #include #include #include #include #include #include #include #include #include using std::string; using std::map; Itemfield_Selection::Itemfield_Selection(AW_selection_list *sellist_, GBDATA *gb_key_data, long type_filter_, SelectableFields field_filter_, ItemSelector& selector_) : AW_DB_selection(sellist_, gb_key_data), type_filter(type_filter_), field_filter(field_filter_), selector(selector_) { it_assert(&selector); } void Itemfield_Selection::fill() { if (field_filter & SF_PSEUDO) { insert_plain(PSEUDO_FIELD_ANY_FIELD); insert_plain(PSEUDO_FIELD_ALL_FIELDS); insert_plain(PSEUDO_FIELD_ANY_FIELD_REC); insert_plain(PSEUDO_FIELD_ALL_FIELDS_REC); } GBDATA *gb_key_data = get_gbd(); GB_transaction ta(gb_key_data); for (GBDATA *gb_key = GB_entry(gb_key_data, CHANGEKEY); gb_key; gb_key = GB_nextEntry(gb_key)) { GBDATA *gb_key_type = GB_entry(gb_key, CHANGEKEY_TYPE); GB_TYPES key_type = GB_TYPES(GB_read_int(gb_key_type)); if (shall_display_type(key_type)) { GBDATA *gb_key_name = GB_entry(gb_key, CHANGEKEY_NAME); if (gb_key_name) { const char *name = GB_read_char_pntr(gb_key_name); if (!name) { fprintf(stderr, "WARNING: can't read key name (Reason: %s)", GB_await_error()); name = ""; } long *hiddenPtr = GBT_read_int(gb_key, CHANGEKEY_HIDDEN); const char *display = NULp; if (!hiddenPtr) { // it's an older db version w/o hidden flag -> add it GB_ERROR error = GBT_write_int(gb_key, CHANGEKEY_HIDDEN, 0); // default is "not hidden" if (error) GB_warningf("WARNING: can't create " CHANGEKEY_HIDDEN " (Reason: %s)\n", error); static long not_hidden = 0; hiddenPtr = ¬_hidden; } if (*hiddenPtr) { // hidden ? if (field_filter & SF_HIDDEN) { // show hidden fields ? display = GBS_global_string("[hidden] %s", name); } } else display = name; if (display) { if (field_filter & SF_SHOW_TYPE) { // prefix type-char display = GBS_global_string("%c: %s", GB_type_2_char(key_type), display); } insert(display, name); } } } } if (!get_sellist()->get_default_value()) { insert_default((field_filter & SF_ALLOW_NEW) ? "" : "", NO_FIELD_SELECTED); } } Itemfield_Selection *FieldSelDef::build_sel(AW_selection_list *from_sellist) const { GBDATA *gb_key_data; { GB_transaction ta(gb_main); gb_key_data = GB_search(gb_main, selector->change_key_path, GB_CREATE_CONTAINER); } return new Itemfield_Selection(from_sellist, gb_key_data, type_filter, field_filter, *selector); } const int FIELDNAME_VISIBLE_CHARS = 20; const int NEWFIELD_EXTRA_SPACE = 13; // max: " (new STRING)" static struct { GB_TYPES type; const char *label; const char *mnemonic; const char *typeName; } creatable[] = { { GB_INT, "Rounded numerical", "i", "INT" }, { GB_FLOAT, "Floating-point n.", "F", "FLOAT" }, { GB_STRING, "Ascii text", "s", "STRING" } // keep in sync with ../DB_UI/ui_species.cxx@FIELD_TYPE_DESCRIPTIONS }; class RegFieldSelection; typedef map FieldSelectionRegistry; class RegFieldSelection { FieldSelDef def; RefPtr aw_popup; static FieldSelectionRegistry registry; static MutableItemSelector NULL_selector; public: bool inAwarChange; RegFieldSelection() : def("dummy", NULp, NULL_selector, 0), aw_popup(NULp) {} RegFieldSelection(const FieldSelDef& def_) : def(def_), aw_popup(NULp), inAwarChange(false) {} const FieldSelDef& get_def() const { return def; } bool new_fields_allowed() const { return def.new_fields_allowed(); } const char *get_field_awarname() const { return def.get_awarname().c_str(); } const char *get_button_awarname() const { return GBS_global_string("tmp/%s/button", get_field_awarname()); } const char *get_type_awarname() const { return GBS_global_string("%s_type", get_field_awarname()); } void popup_window(AW_root *awr); void popdown() { if (aw_popup) aw_popup->hide(); } void create_window(AW_root *awr); void init_awars(AW_root *awr); GB_TYPES get_keytype(const char *fieldName) const { /*! detect type of (defined) key * @param fieldName name of field * @return GB_NONE for undefined key or type of defined key */ GB_TYPES definedType = GB_NONE; GBDATA *gb_main = def.get_gb_main(); GB_transaction ta(gb_main); GBDATA *gb_key = GBT_get_changekey(gb_main, fieldName, def.get_itemtype().change_key_path); if (gb_key) { long *elem_type = GBT_read_int(gb_key, CHANGEKEY_TYPE); if (elem_type) definedType = GB_TYPES(*elem_type); } return definedType; } GB_TYPES get_selected_type(AW_root *awr) { return GB_TYPES(awr->awar(get_type_awarname())->read_int()); } GB_TYPES get_unmated_type() { // if exactly ONE type is allowed -> return that type // return GB_NONE otherwise long allowed = def.get_type_filter(); for (unsigned i = 0; isecond; } static void update_buttons(); }; FieldSelectionRegistry RegFieldSelection::registry; MutableItemSelector RegFieldSelection::NULL_selector; const char *get_itemfield_type_awarname(const char *itemfield_awarname) { /*! returns the corresponding type-awar for an itemfield_awarname. * Only returns an awarname if * - itemfield_awarname was used in create_itemfield_selection_button and * - new fields are allowed there * returns NULp otherwise. */ RegFieldSelection *registered = RegFieldSelection::find(itemfield_awarname); if (registered && registered->new_fields_allowed()) { return registered->get_type_awarname(); } return NULp; } const char *prepare_and_get_selected_itemfield(AW_root *awr, const char *awar_name, GBDATA *gb_main, const ItemSelector& itemtype, FailIfField failIf) { /*! Reads awar used in create_itemfield_selection_button(). * * If the user selected to create a new itemfield, the changekey is created. * * @param awr app root * @param awar_name name of awar used for create_itemfield_selection_button() * @param gb_main database * @param itemtype item type * @param failIf toggles various error conditions (defaults to FIF_STANDARD) * * @return name of the itemfield or NULp * * When NULp is returned * - an error IS ALWAYS exported if (failIf&FIF_NO_FIELD_SELECTED), which is included in FIF_STANDARD! * - an error MAY BE exported otherwise (use GB_have_error() in that case) */ it_assert(!GB_have_error()); RegFieldSelection *selected = RegFieldSelection::find(awar_name); GB_ERROR error = NULp; const char *value = NULp; if (!selected) { error = GBS_global_string("Awar '%s' is not registered as field selection", awar_name); } else { AW_awar *awar_field = awr->awar(selected->get_field_awarname()); const char *field = awar_field->read_char_pntr(); const char *kindOfField = selected->get_def().get_described_field().c_str(); if (!field[0]) field = NO_FIELD_SELECTED; if (strcmp(field, NO_FIELD_SELECTED) == 0) { if (failIf & FIF_NO_FIELD_SELECTED) { error = GBS_global_string("Please select a %s", kindOfField); } } else if (strcmp(field, "name") == 0 && (failIf & FIF_NAME_SELECTED)) { error = GBS_global_string("You may not select 'name' as %s.", kindOfField); // protect user from overwriting the species ID } else { // an allowed fieldname is selected GB_TYPES type = selected->get_keytype(field); if (type == GB_NONE) { // missing field if (selected->new_fields_allowed()) { // allowed to create? error = GB_check_hkey(field); if (!error) { GB_TYPES wantedType = selected->get_selected_type(awr); if (wantedType == GB_NONE) { error = GBS_global_string("Please select the datatype for the new %s '%s'", kindOfField, field); } else { error = GBT_add_new_changekey_to_keypath(gb_main, field, wantedType, itemtype.change_key_path); } } } else { error = GBS_global_string("Selected %s '%s' is not defined (logical error)", kindOfField, field); // should not occur! } } else { // existing field bool typeAllowed = selected->get_def().get_type_filter() & (1<awar(get_button_awarname()); const char *fieldName = awr->awar(get_field_awarname())->read_char_pntr(); if (strcmp(fieldName, NO_FIELD_SELECTED) == 0 || !fieldName[0]) { awar_button->write_string(""); } else { GB_TYPES type = get_keytype(fieldName); bool exists = type != GB_NONE; if (!exists) { type = GB_TYPES(awr->awar(get_type_awarname())->read_int()); } const char *typeName = NULp; for (unsigned i = 0; iwrite_string(GBS_global_string(exists ? "%s (%s)" : "%s (new %s)", fieldName, typeName)); } else { awar_button->write_string(GBS_global_string(" '%s'", fieldName)); } } } static void fieldname_changed_cb(AW_root *awr, RegFieldSelection *fsel) { if (!fsel->inAwarChange) { LocallyModify avoidRecursion(fsel->inAwarChange, true); AW_awar *awar_type = awr->awar(fsel->get_type_awarname()); AW_awar *awar_name = awr->awar(fsel->get_field_awarname()); GB_TYPES defined = fsel->get_keytype(awar_name->read_char_pntr()); if (defined) { awar_type->write_int(defined); fsel->popdown(); } else { // if allowed type of field is determined -> set it // otherwise set type to 'undefined' GB_TYPES determined = fsel->get_unmated_type(); awar_type->write_int(determined); if (determined != GB_NONE) fsel->popdown(); } fsel->update_button_text(awr); } } static bool fieldtype_change_warning = true; static void fieldtype_changed_cb(AW_root *awr, RegFieldSelection *fsel) { if (!fsel->inAwarChange) { LocallyModify avoidRecursion(fsel->inAwarChange, true); AW_awar *awar_name = awr->awar(fsel->get_field_awarname()); GB_TYPES defined = fsel->get_keytype(awar_name->read_char_pntr()); if (defined != GB_NONE) { AW_awar *awar_type = awr->awar(fsel->get_type_awarname()); GB_TYPES selected = GB_TYPES(awar_type->read_int()); if (selected != defined) { awar_type->write_int(defined); if (fieldtype_change_warning) { aw_message("You cannot change the type of an existing field"); } } } fsel->update_button_text(awr); } } void RegFieldSelection::init_awars(AW_root *awr) { if (new_fields_allowed()) { // extra awars needed for popup-style + callbacks awr->awar_string(get_button_awarname(), "?", AW_ROOT_DEFAULT); AW_awar *awar_type = awr->awar_int(get_type_awarname(), GB_NONE, AW_ROOT_DEFAULT); AW_awar *awar_name = awr->awar(get_field_awarname()); awar_type->add_callback(makeRootCallback(fieldtype_changed_cb, this)); awar_name->add_callback(makeRootCallback(fieldname_changed_cb, this)); LocallyModify dontWarn(fieldtype_change_warning, false); awar_type->touch(); // refresh button-text (do NOT use awar_name to do that!) } } void RegFieldSelection::update_buttons() { for (FieldSelectionRegistry::const_iterator reg = registry.begin(); reg != registry.end(); ++reg) { const RegFieldSelection& fsel = reg->second; if (fsel.new_fields_allowed()) { fsel.update_button_text(AW_root::SINGLETON); } } } RegFieldSelection& RegFieldSelection::registrate(AW_root *awr, const FieldSelDef& def) { const string& field_awarname = def.get_awarname(); RegFieldSelection *found = find(field_awarname); if (found) { bool compatible = !found->get_def().matches4reuse(def); if (!compatible) { aw_message(GBS_global_string("Incompatible field-selections defined for awar '%s'", field_awarname.c_str())); it_assert(0); // to use multiple field selections on the same awar, you need to use similar parameters! // (otherwise the user might outsmart the defined restrictions by using the other field-selector) } } else { registry[field_awarname] = RegFieldSelection(def); found = find(field_awarname); found->init_awars(awr); GBDATA *gb_main = def.get_gb_main(); GB_transaction ta(gb_main); GBDATA *gb_key_data = GB_search(gb_main, def.get_itemtype().change_key_path, GB_FIND); if (gb_key_data) { aw_message_if(GB_ensure_callback(gb_key_data, GB_CB_CHANGED, makeDatabaseCallback(RegFieldSelection::update_buttons))); } } it_assert(found); return *found; } void RegFieldSelection::create_window(AW_root *awr) { it_assert(!aw_popup); aw_popup = new AW_window_simple; const bool allowNewFields = new_fields_allowed(); { const char *format = allowNewFields ? "Select or create a new %s" : "Select the %s"; const char *title = GBS_global_string(format, def.get_described_field().c_str()); aw_popup->init(awr, "SELECT_FIELD", title); } if (allowNewFields) aw_popup->load_xfig("awt/field_sel_new.fig"); // Do not DRY (ressource checker!) else aw_popup->load_xfig("awt/field_sel.fig"); aw_popup->at("sel"); const bool FALLBACK2DEFAULT = !allowNewFields; Itemfield_Selection *itemSel; { AW_selection_list *sellist = aw_popup->create_selection_list(get_field_awarname(), 1, 1, FALLBACK2DEFAULT); itemSel = def.build_sel(sellist); itemSel->refresh(); } aw_popup->at("close"); aw_popup->callback(AW_POPDOWN); aw_popup->create_button("CLOSE", "CLOSE", "C"); AW_awar *awar_field = awr->awar(get_field_awarname()); // user-awar if (allowNewFields) { aw_popup->at("help"); aw_popup->callback(makeHelpCallback("field_sel_new.hlp")); aw_popup->create_button("HELP", "HELP", "H"); long allowedTypes = def.get_type_filter(); int possibleTypes = 0; int firstType = -1; for (unsigned i = 0; i0); aw_popup->at("name"); aw_popup->create_input_field(get_field_awarname(), FIELDNAME_VISIBLE_CHARS); // show type selector even if only one type selectable (layout- and informative-purposes) aw_popup->at("type"); aw_popup->create_toggle_field(get_type_awarname(), NULp, NULp); for (unsigned i = 0; iinsert_toggle(creatable[i].label, creatable[i].mnemonic, int(creatable[i].type)); } } if (get_unmated_type() == GB_NONE) { // type may vary -> allowed it to be undefined aw_popup->insert_toggle("", "", GB_NONE); } aw_popup->update_toggle_field(); } else { awar_field->add_callback(makeRootCallback(awt_auto_popdown_cb, &*aw_popup)); } } void RegFieldSelection::popup_window(AW_root *awr) { if (!aw_popup) create_window(awr); it_assert(aw_popup); aw_popup->recalc_pos_atShow(AW_REPOS_TO_MOUSE); // always popup at current mouse-position (i.e. directly above the button) aw_popup->recalc_size_atShow(AW_RESIZE_USER); // if user changes the size of any field-selection popup, that size will be used for future popups aw_popup->activate(); } static void popup_field_selection(AW_window *aw_parent, RegFieldSelection *fsel) { fsel->popup_window(aw_parent->get_root()); } void create_itemfield_selection_button(AW_window *aws, const FieldSelDef& selDef, const char *at) { /*! Create a button that pops up a window allowing to select an item-field. * Field may exist or not. Allows to specify type of the new field in the latter case. * * @param aws parent window * @param selDef specifies details of field selection * @param at xfig label (ignored if NULp) * * The length of the button is hardcoded and depends on whether new fields are possible or not. * Nevertheless you may override its size by defining an at-label with 'to:'-position in xfig. * * At start of field-writing code: * - use prepare_and_get_selected_itemfield() to create the changekey-info (needed for new fields only) * * For each item written to: * - use GBT_searchOrCreate_itemfield_according_to_changekey() to create the field * - use GB_write_lossless_...() to write its value */ if (at) aws->at(at); RegFieldSelection& fsel = RegFieldSelection::registrate(aws->get_root(), selDef); int old_button_length = aws->get_button_length(); aws->button_length(FIELDNAME_VISIBLE_CHARS+(selDef.new_fields_allowed() ? NEWFIELD_EXTRA_SPACE : 0)); aws->callback(makeWindowCallback(popup_field_selection, &fsel)); char *id = GBS_string_eval(selDef.get_awarname().c_str(), "/=_"); aws->create_button(GBS_global_string("select_%s", id), selDef.new_fields_allowed() ? fsel.get_button_awarname() : fsel.get_field_awarname()); free(id); aws->button_length(old_button_length); } Itemfield_Selection *create_itemfield_selection_list(AW_window *aws, const FieldSelDef& selDef, const char *at) { /*! Create a selection list allowing to select an item-field. * Similar to create_itemfield_selection_button(). * * Differences: * - returns Itemfield_Selection -> allows to query list of fields (e.g. used in "Reorder fields") * - does not allow to select a new, not yet existing field */ if (at) aws->at(at); const bool FALLBACK2DEFAULT = true; it_assert(selDef.new_fields_allowed() == false); // to allow creation of new fields -> use create_itemfield_selection_button RegFieldSelection::registrate(aws->get_root(), selDef); // to avoid that awar is misused for multiple incompatible field-selections AW_selection_list *sellist = aws->create_selection_list(selDef.get_awarname().c_str(), FALLBACK2DEFAULT); Itemfield_Selection *selection = selDef.build_sel(sellist); selection->refresh(); return selection; } // -------------------------------------------------------------------------------- #ifdef UNIT_TESTS #ifndef TEST_UNIT_H #include #endif void TEST_lossless_conversion() { GB_shell shell; GBDATA *gb_main = GB_open("no.arb", "c"); { GB_transaction ta(gb_main); const long tested_filter[] = { FIELD_FILTER_INT_WRITEABLE, FIELD_FILTER_BYTE_WRITEABLE, FIELD_FILTER_FLOAT_WRITEABLE, }; for (unsigned f = 0; f