]> git.decadent.org.uk Git - ion3.git/blob - ioncore/names.c
5f3c386d8eab7337fd9b5be31c63b8aba9f014aa
[ion3.git] / ioncore / names.c
1 /*
2  * ion/ioncore/names.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12
13 #include <string.h>
14 #include <limits.h>
15
16 #include <libtu/rb.h>
17 #include <libtu/minmax.h>
18 #include <libtu/objp.h>
19 #include <libextl/extl.h>
20
21 #include "common.h"
22 #include "global.h"
23 #include "region.h"
24 #include "clientwin.h"
25 #include "names.h"
26 #include "strings.h"
27 #include "gr.h"
28
29
30 /*{{{ Implementation */
31
32
33 WNamespace ioncore_internal_ns={NULL, FALSE};
34 WNamespace ioncore_clientwin_ns={NULL, FALSE};
35
36
37 static bool initialise_ns(WNamespace *ns)
38 {
39     if(ns->initialised)
40         return TRUE;
41     
42     if(ns->rb==NULL)
43         ns->rb=make_rb();
44     
45     ns->initialised=(ns->rb!=NULL);
46     
47     return ns->initialised;
48 }
49
50
51 static int parseinst(const char *name, const char **startinst)
52 {
53     const char *p2, *p3=NULL;
54     int inst;
55     int l=strlen(name);
56     
57     *startinst=name+l;
58
59     if(name[l-1]!='>')
60         return -1;
61     
62     p2=strrchr(name, '<');
63     if(p2==NULL)
64         return -1;
65     
66     inst=strtoul(p2+1, (char**)&p3, 10);
67     
68     if(inst<0 || p3!=name+l-1)
69         return -1;
70     
71     *startinst=p2;
72     return inst;
73 }
74
75
76 static int parseinst_simple(const char *inststr)
77 {
78     const char *end=NULL;
79     int inst;
80     
81     if(*inststr=='\0')
82         return 0;
83     
84     if(*inststr=='<'){
85         inst=strtoul(inststr+1, (char**)&end, 10);
86     
87         if(inst>=0 && end!=NULL && *end=='>')
88             return inst;
89     }
90
91     warn(TR("Corrupt instance number %s."), inststr);
92     return -1;
93 }
94
95
96 #define COMPARE_FN ((Rb_compfn*)compare_nameinfos)
97
98 static int compare_nameinfos(const WRegionNameInfo *ni1, 
99                              const WRegionNameInfo *ni2)
100 {
101     int l1=0, l2=0;
102     int i1=0, i2=0;
103     int mc=0;
104     
105     /* Handle unnamed regions. */
106
107     if(ni1->name==NULL){
108         if(ni2->name!=NULL)
109             return 1;
110         return (ni1==ni2 ? 0 : (ni1<ni2 ? -1 : 1));
111     }else if(ni2->name==NULL){
112         return -1;
113     }
114
115     /* Special case: inst_off<0 means that -inst_off-1 is the actual
116      * instance number and the name does not contain it.
117      */
118
119     if(ni1->inst_off>=0)
120         l1=ni1->inst_off;
121     else
122         l1=strlen(ni1->name);
123
124     if(ni2->inst_off>=0)
125         l2=ni2->inst_off;
126     else
127         l2=strlen(ni2->name);
128     
129     /* Check name part first */
130     
131     mc=strncmp(ni1->name, ni2->name, minof(l1, l2));
132     
133     if(mc!=0)
134         return mc;
135     
136     if(l1!=l2)
137         return (l1<l2 ? -1 : 1);
138
139     /* Same name, different instance */
140     
141     if(ni1->inst_off>=0)
142         i1=parseinst_simple(ni1->name+ni1->inst_off);
143     else
144         i1=-ni1->inst_off-1; /*???*/
145
146     if(ni2->inst_off>=0)
147         i2=parseinst_simple(ni2->name+ni2->inst_off);
148     else
149         i2=-ni2->inst_off-1; /*???*/
150
151     if(i1!=i2)
152         return (i1<i2 ? -1 : 1);
153     
154     /* Same name and instance */
155     
156     return 0;
157 }
158
159 static bool insert_reg(Rb_node tree, WRegion *reg)
160 {
161     assert(reg->ni.node==NULL);
162     reg->ni.node=(void*)rb_insertg(tree, &(reg->ni), reg, COMPARE_FN);
163     return (reg->ni.node!=NULL);
164 }
165
166 static bool separated(const WRegionNameInfo *ni1, 
167                       const WRegionNameInfo *ni2, int *i1ret)
168 {
169     int l1, l2;
170     int i1, i2;
171     int mc;
172     
173     assert(ni1->name!=NULL);
174     
175     if(ni2->name==NULL){
176         /* Shouldn't happen the way this function is called below; unnamed
177          * regions are before others in the traversal order of the tree.
178          */
179         *i1ret=parseinst_simple(ni1->name+ni1->inst_off);
180         return TRUE;
181     }
182     
183     assert(ni1->inst_off>=0 && ni2->inst_off>=0);
184
185     l1=ni1->inst_off;
186     l2=ni2->inst_off;
187
188     i1=parseinst_simple(ni1->name+ni1->inst_off);
189     *i1ret=i1;
190     
191     if(l1!=l2)
192         return TRUE;
193         
194     if(memcmp(ni1->name, ni2->name, l1)!=0)
195         return TRUE;
196     
197     i2=parseinst_simple(ni2->name+ni2->inst_off);
198     
199     return (i2>(i1+1));
200 }
201
202
203
204 void region_unregister(WRegion *reg)
205 {
206     if(reg->ni.node!=NULL){
207         rb_delete_node((Rb_node)reg->ni.node);
208         reg->ni.node=NULL;
209     }
210     
211     if(reg->ni.name!=NULL){
212         free(reg->ni.name);
213         reg->ni.name=NULL;
214         reg->ni.inst_off=0;
215     }
216 }
217
218
219 static bool make_full_name(WRegionNameInfo *ni, const char *name, int inst, 
220                            bool append_always)
221 {
222     assert(ni->name==NULL);
223     
224     if(inst==0 && !append_always)
225         ni->name=scopy(name);
226     else
227         libtu_asprintf(&(ni->name), "%s<%d>", name, inst);
228         
229     if(ni->name==NULL)
230         return FALSE;
231     
232     ni->inst_off=strlen(name);
233     
234     return TRUE;
235 }
236
237
238 static bool do_use_name(WRegion *reg, WNamespace *ns, const char *name,
239                         int instrq, bool failchange)
240 {
241     int parsed_inst=-1;
242     WRegionNameInfo ni={NULL, 0, NULL};
243     const char *dummy=NULL;
244     Rb_node node;
245     int inst=-1;
246     int found=0;
247
248     parsed_inst=parseinst(name, &dummy);
249     
250     if(!ns->initialised)
251         return FALSE;
252
253     /* If there's something that looks like an instance at the end of
254      * name, we will append the instance number.
255      */
256     if(parsed_inst>=0 && inst==0 && failchange)
257         return FALSE;
258     
259     if(instrq>=0){
260         WRegionNameInfo tmpni;
261         tmpni.name=(char*)name;
262         tmpni.inst_off=-instrq-1;
263         node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
264         if(found){
265             if(rb_val(node)==(void*)reg){
266                 /* The region already has the requested name */
267                 return TRUE;
268             }
269             if(failchange)
270                 return FALSE;
271         }else{
272             inst=instrq;
273         }
274     }
275     
276     if(inst<0){
277         WRegionNameInfo tmpni;
278         
279         found=0;
280         inst=0;
281
282         tmpni.name=(char*)name;
283         tmpni.inst_off=-1;
284         node=rb_find_gkey_n(ns->rb, &tmpni, COMPARE_FN, &found);
285         
286         if(found){
287             while(1){
288                 Rb_node next=rb_next(node);
289                 
290                 if(rb_val(node)==(void*)reg){
291                     /* The region already has a name of requested form */
292                     return TRUE;
293                 }
294                 
295                 if(next==rb_nil(ns->rb) ||
296                    separated((const WRegionNameInfo*)node->k.key, 
297                              (const WRegionNameInfo*)next->k.key, &inst)){
298                     /* 'inst' should be next free instance after increment
299                      * as separation was greater then one.
300                      */
301                     inst++;
302                     break;
303                 }
304
305                 /* 'inst' should be instance of next after increment 
306                  * as separation was one.
307                  */
308                 inst++;
309                 node=next;
310             }
311         }
312     }
313
314     if(!make_full_name(&ni, name, inst, parsed_inst>=0))
315         return FALSE;
316
317     /*
318     rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
319     
320     assert(!found);
321      */
322
323     region_unregister(reg);
324     
325     reg->ni.name=ni.name;
326     reg->ni.inst_off=ni.inst_off;
327     
328     if(!insert_reg(ns->rb, reg)){
329         free(reg->ni.name);
330         reg->ni.name=NULL;
331         reg->ni.inst_off=0;
332         return FALSE;
333     }
334
335     return TRUE;
336 }
337
338
339 static bool use_name_anyinst(WRegion *reg, WNamespace *ns, const char *name)
340 {
341     return do_use_name(reg, ns, name, -1, FALSE);
342 }
343
344
345 static bool use_name_exact(WRegion *reg, WNamespace *ns, const char *name)
346 {
347     return do_use_name(reg, ns, name, 0, TRUE);
348 }
349
350
351 static bool use_name_parseany(WRegion *reg, WNamespace *ns, const char *name)
352 {
353     int l, inst;
354     const char *startinst;
355     
356     l=strlen(name);
357     
358     inst=parseinst(name, &startinst);
359     if(inst>=0){
360         bool retval=FALSE;
361         int realnamelen=startinst-name;
362         char *realname=ALLOC_N(char, realnamelen+1);
363         if(realname!=NULL){
364             memcpy(realname, name, realnamelen);
365             realname[realnamelen]='\0';
366             retval=do_use_name(reg, ns, realname, inst, FALSE);
367             free(realname);
368         }
369         return retval;
370     }
371     
372     return do_use_name(reg, ns, name, 0, FALSE);
373 }
374
375
376
377 /*}}}*/
378
379
380 /*{{{ Interface */
381
382
383 /*EXTL_DOC
384  * Returns the name for \var{reg}.
385  */
386 EXTL_SAFE
387 EXTL_EXPORT_MEMBER
388 const char *region_name(WRegion *reg)
389 {
390     return reg->ni.name;
391 }
392
393
394 static bool do_set_name(bool (*fn)(WRegion *reg, WNamespace *ns, const char *p),
395                         WRegion *reg, WNamespace *ns, const char *p)
396 {
397     bool ret=TRUE;
398     char *nm=NULL;
399
400     if(!initialise_ns(ns))
401         return FALSE;
402     
403     if(p!=NULL){
404         nm=scopy(p);
405         if(nm==NULL)
406             return FALSE;
407         str_stripws(nm);
408     }
409
410     if(nm==NULL || *nm=='\0'){
411         region_unregister(reg);
412         ret=insert_reg(ns->rb, reg);
413     }else{
414         ret=fn(reg, ns, nm);
415     }
416     
417     if(nm!=NULL)
418         free(nm);
419
420     region_notify_change(reg, ioncore_g.notifies.name);
421     
422     return ret;
423 }
424
425
426 bool region_register(WRegion *reg)
427 {
428     assert(reg->ni.name==NULL);
429     
430     if(!initialise_ns(&ioncore_internal_ns))
431         return FALSE;
432     
433     return use_name_anyinst(reg, &ioncore_internal_ns, OBJ_TYPESTR(reg));
434 }
435
436
437 bool clientwin_register(WClientWin *cwin)
438 {
439     WRegion *reg=(WRegion*)cwin;
440     
441     assert(reg->ni.name==NULL);
442     
443     if(!initialise_ns(&ioncore_clientwin_ns))
444         return FALSE;
445     
446     return insert_reg(ioncore_clientwin_ns.rb, (WRegion*)cwin);
447 }
448
449
450 /*EXTL_DOC
451  * Set the name of \var{reg} to \var{p}. If the name is already in use,
452  * an instance number suffix \code{<n>} will be attempted. If \var{p} has
453  * such a suffix, it will be modified, otherwise such a suffix will be
454  * added. Setting \var{p} to nil will cause current name to be removed.
455  */
456 EXTL_EXPORT_MEMBER
457 bool region_set_name(WRegion *reg, const char *p)
458 {
459 /*    return do_set_name(use_name_parseany, reg, &ioncore_internal_ns, p);*/
460     return do_set_name(use_name_parseany, reg, 
461             OBJ_IS(reg, WClientWin) ? &ioncore_clientwin_ns : &ioncore_internal_ns,
462             p);
463 }
464
465
466 /*EXTL_DOC
467  * Similar to \fnref{WRegion.set_name} except if the name is already in use,
468  * other instance numbers will not be attempted. The string \var{p} should
469  * not contain a \code{<n>} suffix or this function will fail.
470  */
471 EXTL_EXPORT_MEMBER
472 bool region_set_name_exact(WRegion *reg, const char *p)
473 {
474     return do_set_name(use_name_exact, reg, &ioncore_internal_ns, p);
475 }
476
477
478 bool clientwin_set_name(WClientWin *cwin, const char *p)
479 {
480     return do_set_name(use_name_anyinst, (WRegion*)cwin,
481                        &ioncore_clientwin_ns, p);
482 }
483
484
485 /*}}}*/
486
487
488 /*{{{ Lookup and list */
489
490
491 static WRegion *do_lookup_region(WNamespace *ns, const char *cname,
492                                  const char *typenam)
493 {
494     WRegionNameInfo ni;
495     Rb_node node;
496     int found=0;
497     const char *instptr=NULL;
498
499     if(cname==NULL || !ns->initialised)
500         return NULL;
501     
502     parseinst(cname, &instptr);
503     assert(instptr!=NULL);
504     
505     ni.name=(char*)cname;
506     ni.inst_off=instptr-cname;
507     
508     node=rb_find_gkey_n(ns->rb, &ni, COMPARE_FN, &found);
509     
510     if(!found)
511         return NULL;
512     
513     return (WRegion*)node->v.val;
514 }
515
516
517 /*EXTL_DOC
518  * Attempt to find a non-client window region with name \var{name} and type
519  * inheriting \var{typenam}.
520  */
521 EXTL_SAFE
522 EXTL_EXPORT
523 WRegion *ioncore_lookup_region(const char *name, const char *typenam)
524 {
525     return do_lookup_region(&ioncore_internal_ns, name, typenam);
526 }
527
528
529 /*EXTL_DOC
530  * Attempt to find a client window with name \var{name}.
531  */
532 EXTL_SAFE
533 EXTL_EXPORT
534 WClientWin *ioncore_lookup_clientwin(const char *name)
535 {
536     return (WClientWin*)do_lookup_region(&ioncore_clientwin_ns, name, 
537                                          "WClientWin");
538 }
539
540
541 static bool do_list(ExtlFn fn, WNamespace *ns, const char *typenam)
542 {
543     Rb_node node;
544     int n=0;
545     
546     if(!ns->initialised)
547         return FALSE;
548     
549     rb_traverse(node, ns->rb){
550         WRegion *reg=(WRegion*)node->v.val;
551         
552         assert(reg!=NULL);
553
554         if(typenam!=NULL && !obj_is_str((Obj*)reg, typenam))
555             continue;
556
557         if(!extl_iter_obj(fn, (Obj*)reg))
558             return FALSE;
559     }
560     
561     return TRUE;
562 }
563
564
565 /*EXTL_DOC
566  * Iterate over all non-client window regions with (inherited) class
567  * \var{typenam} until \var{iterfn} returns \code{false}.
568  * The function itself returns \code{true} if it reaches the end of list
569  * without this happening.
570  */
571 EXTL_SAFE
572 EXTL_EXPORT
573 bool ioncore_region_i(ExtlFn fn, const char *typenam)
574 {
575     return do_list(fn, &ioncore_internal_ns, typenam);
576 }
577
578
579 /*EXTL_DOC
580  * Iterate over client windows until \var{iterfn} returns \code{false}.
581  * The function itself returns \code{true} if it reaches the end of list
582  * without this happening.
583  */
584 EXTL_SAFE
585 EXTL_EXPORT
586 bool ioncore_clientwin_i(ExtlFn fn)
587 {
588     return do_list(fn, &ioncore_clientwin_ns, NULL);
589 }
590
591
592 /*}}}*/
593
594
595 /*{{{ Displayname */
596
597
598 const char *region_displayname(WRegion *reg)
599 {
600     const char *ret=NULL;
601     CALL_DYN_RET(ret, const char *, region_displayname, reg, (reg));
602     return ret;
603 }
604
605
606 char *region_make_label(WRegion *reg, int maxw, GrBrush *brush)
607 {
608     const char *name=region_displayname(reg);
609
610     if(name==NULL)
611         return NULL;
612     
613     return grbrush_make_label(brush, name, maxw);
614 }
615
616
617 /*}}}*/