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