source: branches/profile/AWT/AWT_TreeAwars.cxx

Last change on this file was 11529, checked in by westram, 10 years ago
  • use GB_atclose (instead of GB_atexit) to destroy TreeAwarRegistry
    • fixes crash in branch 'gtk_only'
    • allow to remove internal hack (ignore_db_callbacks)
File size: 7.8 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : AWT_TreeAwars.cxx                                 //
4//   Purpose   : tree awar registry                                //
5//                                                                 //
6//   Coded by Ralf Westram (coder@reallysoft.de) in January 2014   //
7//   Institute of Microbiology (Technical University Munich)       //
8//   http://www.arb-home.de/                                       //
9//                                                                 //
10// =============================================================== //
11
12#include "awt_TreeAwars.hxx"
13
14#include <arbdbt.h>
15#include <ad_cb.h>
16
17#include <arb_global_defs.h>
18
19#include <aw_awar.hxx>
20#include <aw_root.hxx>
21#include <aw_global_awars.hxx>
22
23#include <set>
24#include <cstddef>
25
26#define awt_assert(cond) arb_assert(cond)
27
28/*! bind a callback to a tree-awar and the associated tree
29 * i.e. callback is triggered if awar changes to different tree
30 * or if tree itself changes.
31 *
32 * The awar has to be type string and has to contain a tree name
33 */
34class BoundTreeAwarCallback : virtual Noncopyable {
35    mutable AW_awar  *awar;
36    GBDATA           *gb_tree;
37    TreeAwarCallback  cb;
38    mutable bool      triggerOnDataChange;
39
40public:
41    BoundTreeAwarCallback(AW_awar *awar_, const TreeAwarCallback& cb_, bool triggerIfTreeDataChanges);
42    ~BoundTreeAwarCallback();
43
44    void bind_tree_callback();
45    void call(bool treeDataChanged) { cb(awar, treeDataChanged); }
46
47    void rename_if(const char *oldname, const char *newname) const {
48        if (strcmp(awar->read_char_pntr(), oldname) == 0) {
49            awar->write_string(newname);
50        }
51    }
52
53    bool is_less_than(const BoundTreeAwarCallback& other) const {
54        ptrdiff_t diff = (const char*)awar - (const char*)other.awar;
55        return diff<0;
56    }
57
58    void tree_lost() { gb_tree = NULL; }
59};
60
61typedef SmartPtr<BoundTreeAwarCallback> BoundTreeAwarCallbackPtr;
62
63inline bool operator<(const BoundTreeAwarCallbackPtr& bc1, const BoundTreeAwarCallbackPtr& bc2) {
64    return bc1->is_less_than(*bc2);
65}
66
67typedef std::set<BoundTreeAwarCallbackPtr> BoundTreeAwarCallbacks;
68
69/*! registry for TreeAwars
70 */
71class TreeAwarRegistry : virtual Noncopyable {
72    GBDATA                 *gb_main;
73    BoundTreeAwarCallbacks  callbacks;
74
75public:
76    TreeAwarRegistry(GBDATA *gb_main_);
77
78    static SmartPtr<TreeAwarRegistry> SINGLETON;
79
80    GBDATA *get_gb_main() { return gb_main; }
81    void add(BoundTreeAwarCallbackPtr bcb) { callbacks.insert(bcb); }
82
83    void tree_renamed(const char *oldname, const char *newname);
84};
85
86SmartPtr<TreeAwarRegistry> TreeAwarRegistry::SINGLETON;
87
88static void destroy_TreeAwarRegistry(GBDATA*,void*) {
89    TreeAwarRegistry::SINGLETON.SetNull();
90}
91
92// --------------------------
93//      BoundTreeAwarCallback
94
95static void TreeDataChanged_cb(UNFIXED, BoundTreeAwarCallback *tac, GB_CB_TYPE cbtype) {
96    if (cbtype == GB_CB_DELETE) {
97        tac->tree_lost();
98    }
99    tac->call(true);
100}
101static void TreeAwarChanged_cb(UNFIXED, BoundTreeAwarCallback *tac) {
102    tac->bind_tree_callback();
103    tac->call(false);
104}
105
106void BoundTreeAwarCallback::bind_tree_callback() {
107    if (triggerOnDataChange) {
108        DatabaseCallback dbcb = makeDatabaseCallback(TreeDataChanged_cb, this);
109        {
110            GBDATA         *gb_main = TreeAwarRegistry::SINGLETON->get_gb_main();
111            GB_transaction  ta(gb_main);
112            if (gb_tree) {
113                GB_remove_callback(gb_tree, GB_CB_CHANGED_OR_DELETED, dbcb);
114                gb_tree = NULL;
115            }
116
117            char *treename = awar->read_string();
118            gb_tree        = GBT_find_tree(gb_main, treename);
119
120            if (gb_tree) GB_add_callback(gb_tree, GB_CB_CHANGED_OR_DELETED, dbcb);
121            free(treename);
122        }
123    }
124}
125
126BoundTreeAwarCallback::BoundTreeAwarCallback(AW_awar *awar_, const TreeAwarCallback& cb_, bool triggerIfTreeDataChanges)
127    : awar(awar_),
128      gb_tree(NULL),
129      cb(cb_),
130      triggerOnDataChange(triggerIfTreeDataChanges)
131{
132    awar->add_callback(makeRootCallback(TreeAwarChanged_cb, this));
133    bind_tree_callback();
134}
135BoundTreeAwarCallback::~BoundTreeAwarCallback() {
136    awar->remove_callback(makeRootCallback(TreeAwarChanged_cb, this));
137
138    // remove DB callback w/o triggering it:
139    char *old = awar->read_string();
140    awar->write_string("");
141    bind_tree_callback();
142    awar->write_string(old);
143    free(old);
144}
145
146// --------------------------
147//      TreeAwarRegistry
148
149static void tree_renamed_cb(AW_root *aw_root) {
150    AW_awar *awar_renamed = aw_root->awar(AWAR_ARB_TREE_RENAMED);
151    char    *name_change  = awar_renamed->read_string();
152
153    if (name_change[0]) {
154        char     *equal   = strchr(name_change, '=');
155        GB_ERROR  error   = NULL;
156        if (!equal) error = "Expected '=' missing";
157        else {
158            equal[0] = 0;
159
160            const char *oldname = name_change;
161            const char *newname = equal+1;
162            TreeAwarRegistry::SINGLETON->tree_renamed(oldname, newname);
163        }
164
165        if (error) {
166            fprintf(stderr, "Warning: Cannot handle invalid tree-name-change ('%s'; %s)",
167                    awar_renamed->read_char_pntr(), error);
168        }
169    }
170}
171
172TreeAwarRegistry::TreeAwarRegistry(GBDATA *gb_main_)
173    : gb_main(gb_main_)
174{
175    // uses global awar AWAR_ARB_TREE_RENAMED to synchronize between multiple ARB apps
176    AW_root::SINGLETON->awar(AWAR_ARB_TREE_RENAMED)->add_callback(tree_renamed_cb);
177    GB_atclose(gb_main_, destroy_TreeAwarRegistry, NULL);
178}
179
180void TreeAwarRegistry::tree_renamed(const char *oldname, const char *newname) {
181    for (BoundTreeAwarCallbacks::iterator bcb = callbacks.begin(); bcb != callbacks.end(); ++bcb) {
182        (*bcb)->rename_if(oldname, newname);
183    }
184}
185
186// -------------------
187//      interface
188
189void AWT_initTreeAwarRegistry(GBDATA *gbmain) {
190    /*! initialize TreeAwarRegistry.
191     *
192     * Has to be called after ARB_init_global_awars().
193     * Will be destroyed on GB_close().
194     *
195     * Allows to use other functions provided by this module.
196     */
197    awt_assert(gbmain);
198    if (TreeAwarRegistry::SINGLETON.isSet()) {
199        if (TreeAwarRegistry::SINGLETON->get_gb_main() != gbmain) {
200            GBK_terminate("double init of TreeAwarRegistry with different gbmain");
201        }
202    }
203    else {
204        TreeAwarRegistry::SINGLETON = new TreeAwarRegistry(gbmain);
205    }
206}
207
208void AWT_registerTreeAwarCallback(AW_awar *awar, const TreeAwarCallback& tacb, bool triggerIfTreeDataChanges) {
209    /*! bind and register TreeAwarCallback
210     *
211     * Signature of TreeAwarCallback is 'void cb(AW_awar *awar, bool treeDataChanged, ...)'
212     * 'treeDataChanged' is true, if the callback is triggered by a change of the tree-data
213     * 'treeDataChanged' is false, if the callback is triggered by a change of the awar
214     *
215     * if 'triggerIfTreeDataChanges' is false, the callback will not trigger if tree-data changes
216     */
217
218    TreeAwarRegistry::SINGLETON->add(new BoundTreeAwarCallback(awar, tacb, triggerIfTreeDataChanges));
219}
220
221static void null_cb() {}
222void AWT_registerTreeAwarSimple(AW_awar *awar) {
223    /*! just register a tree awar, w/o any client cb.
224     * -> awar will just automatically follow tree-renames
225     */
226    AWT_registerTreeAwarCallback(awar, makeTreeAwarCallback(null_cb), false);
227}
228
229static void announce_renamed(const char *oldname, const char *newname) {
230    AW_awar *awar_renamed = AW_root::SINGLETON->awar(AWAR_ARB_TREE_RENAMED);
231    awar_renamed->write_string(GBS_global_string("%s=%s", oldname, newname)); // triggers tree_renamed_cb (in this and all other ARB apps)
232}
233
234void AWT_announce_tree_renamed(const char *oldname, const char *newname) { announce_renamed(oldname, newname); }
235void AWT_announce_tree_deleted(const char *name)                         { announce_renamed(name,    NO_TREE_SELECTED); }
236
Note: See TracBrowser for help on using the repository browser.