1 /***************************************************************************
2  *   Copyright (C) 2010~2010 by CSSlayer                                   *
3  *   wengxt@gmail.com                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
19  ***************************************************************************/
20 
21 /**
22  * @defgroup FcitxConfig FcitxConfig
23  *
24  * FcitxConfig includes a lot of configuration related macro and function.
25  * Macro can be easily used to bind a struct with configuration
26  *
27  * Fcitx configuration file can be easily mapped to corresponding user interface,
28  * and you don't need to write any user interface at all.
29  *
30  * FcitxConfig can be also used to implement native user interface.
31  *
32  * Here is a common example for use macro binding with a struct
33  *
34  * @code
35  *    typedef struct _FcitxProfile {
36  *        FcitxGenericConfig gconfig;
37  *        boolean bUseRemind;
38  *        char* imName;
39  *        boolean bUseWidePunc;
40  *        boolean bUseFullWidthChar;
41  *        boolean bUsePreedit;
42  *        char* imList;
43  *    } FcitxProfile;
44  * @endcode
45  *
46  * A config struct need to put FcitxGenericConfig as first field.
47  * Following code will define a function
48  *
49  * @code
50  * CONFIG_BINDING_BEGIN_WITH_ARG(FcitxProfile, FcitxInstance* instance)
51  * CONFIG_BINDING_REGISTER("Profile", "FullWidth", bUseFullWidthChar)
52  * CONFIG_BINDING_REGISTER("Profile", "UseRemind", bUseRemind)
53  * CONFIG_BINDING_REGISTER_WITH_FILTER_ARG("Profile", "IMName", imName, FilterIMName, instance)
54  * CONFIG_BINDING_REGISTER("Profile", "WidePunc", bUseWidePunc)
55  * CONFIG_BINDING_REGISTER("Profile", "PreeditStringInClientWindow", bUsePreedit)
56  * CONFIG_BINDING_REGISTER_WITH_FILTER_ARG("Profile", "EnabledIMList", imList, FilterIMList, instance)
57  * CONFIG_BINDING_END()
58  * @endcode
59  *
60  * Then you will get following function:
61  *
62  * @code
63  * void FcitxProfileConfigBind( FcitxProfile* config, FcitxConfigFile* cfile, FcitxConfigFileDesc* cfdesc, FcitxInstance* instance )
64  * @endcode
65  *
66  * If you need forward declaration, you can used
67  * @code
68  * CONFIG_BINDING_DECLARE_WITH_ARG(FcitxProfile, FcitxInstance* instance)
69  * @endcode
70  *
71  * The FcitxConfigFileDesc pointer is coresponding to the .desc file, which need to be placed
72  * under share/fcitx/configdesc/
73  *
74  * You can use following macro to define a define to load FcitxConfigFileDesc* pointer,
75  * second argument is the .desc file name.
76  *
77  * The FcitxConfigFileDesc pointer returned by this macro is a static variable, so it should not be
78  * free'd, and will only load once.
79  *
80  * @code
81  * CONFIG_DESC_DEFINE(GetProfileDesc, "profile.desc")
82  * @endcode
83  */
84 
85 /**
86  * @addtogroup FcitxConfig
87  * @{
88  */
89 
90 /**
91  * @file fcitx-config.h
92  * @author CSSlayer wengxt@gmail.com
93  * @date 2010-04-30
94  *
95  * Fcitx configure file read-write
96  */
97 
98 #ifndef _FCITX_FCITX_CONFIG_H_
99 #define _FCITX_FCITX_CONFIG_H_
100 
101 #include <stdint.h>
102 #include <stdio.h>
103 #include <errno.h>
104 #include <fcitx-utils/uthash.h>
105 #include <fcitx-utils/utils.h>
106 #include <fcitx-utils/log.h>
107 #include <fcitx-config/xdg.h>
108 #include <fcitx-config/hotkey.h>
109 
110 #ifdef __cplusplus
111 extern "C"
112 {
113 #endif
114 
115     /**
116      * The Color type in config file
117      **/
118     typedef struct _FcitxConfigColor {
119         double r; /**< red */
120         double g; /**< green */
121         double b; /**< blue */
122     } FcitxConfigColor;
123 
124     /**
125      * config value type
126      **/
127     typedef enum _FcitxConfigType {
128         T_Integer,
129         T_Color,
130         T_String,
131         T_Char,
132         T_Boolean,
133         T_Enum,
134         T_File,
135         T_Hotkey,
136         T_Font,
137         T_I18NString
138     } FcitxConfigType;
139 
140     /**
141      * The sync direction
142      **/
143     typedef enum _FcitxConfigSync {
144         Raw2Value,
145         Value2Raw,
146         ValueFree
147     } FcitxConfigSync;
148 
149     /**
150      * Sync result
151      **/
152     typedef enum _FcitxConfigSyncResult {
153         SyncSuccess,
154         SyncNoBinding,
155         SyncInvalid
156     } FcitxConfigSyncResult;
157 
158     /**
159      * The value of config
160      **/
161     typedef union _FcitxConfigValueType {
162         void *untype; /**< simple pointer */
163         int *integer; /**< pointer to integer */
164         boolean *boolvalue; /**< pointer to boolean */
165 
166         struct _FcitxHotkey *hotkey; /**< pointer to two hotkeys */
167         FcitxConfigColor *color; /**< pointer to color */
168         int *enumerate; /**< pointer to enum */
169         char **string; /**< pointer to string */
170         char *chr; /**< pointer to char */
171     } FcitxConfigValueType;
172 
173     typedef struct _FcitxConfigGroup FcitxConfigGroup; /**< FcitxConfigGroup */
174 
175     typedef struct _FcitxConfigOption FcitxConfigOption; /**< FcitxConfigOption */
176 
177     typedef struct _FcitxConfigFileDesc FcitxConfigFileDesc; /**< FcitxConfigFileDesc */
178 
179     typedef struct _FcitxConfigGroupDesc FcitxConfigGroupDesc; /**< FcitxConfigGroupDesc */
180 
181     typedef struct _FcitxConfigOptionDesc FcitxConfigOptionDesc; /**< FcitxConfigOptionDesc */
182 
183     typedef struct _FcitxConfigOptionDesc2 FcitxConfigOptionDesc2; /**< FcitxConfigOptionDesc2 */
184 
185     typedef struct _FcitxGenericConfig FcitxGenericConfig; /**< FcitxGenericConfig */
186 
187     typedef struct _FcitxConfigOptionSubkey FcitxConfigOptionSubkey; /**< FcitxConfigOptionSubkey */
188 
189     typedef union _FcitxConfigConstrain FcitxConfigConstrain /** < FcitxConfigConstrain */;
190 
191     /**
192      * sync filter function
193      **/
194     typedef void (*FcitxSyncFilter)(FcitxGenericConfig* config, FcitxConfigGroup *group, FcitxConfigOption *option, void* value, FcitxConfigSync sync, void* arg);
195 
196     /**
197      * Enum value type description
198      **/
199     typedef struct _FcitxConfigEnum {
200         char **enumDesc; /**< enum string description, a user visble string */
201         int enumCount; /**< length of enumDesc */
202     } FcitxConfigEnum;
203 
204     /**
205      * Config file contains multiple config groups, and the opposite config file description
206      **/
207     typedef struct _FcitxConfigFile {
208         FcitxConfigFileDesc *fileDesc; /**< configuration file description */
209         FcitxConfigGroup* groups; /**< contained group */
210     } FcitxConfigFile;
211 
212 
213     /**
214      * A generic config struct, config struct can derive from it.
215      * @code
216      *        struct TestConfig {
217      *            FcitxGenericConfig gconfig;
218      *            int own_value;
219      *        };
220      * @endcode
221      **/
222     struct _FcitxGenericConfig {
223         /**
224          * config file pointer
225          **/
226         FcitxConfigFile *configFile;
227     };
228 
229     /**
230      * Config Option Description, it describe a Key=Value entry in config file.
231      **/
232     struct _FcitxConfigOptionDesc {
233         char *optionName; /**< option name */
234         char *desc; /**< optiont description string, user visible */
235         FcitxConfigType type; /**< value type */
236         char *rawDefaultValue; /**< raw string default value */
237         FcitxConfigEnum configEnum; /**< if type is enum, the enum item info */
238 
239         UT_hash_handle hh; /**< hash handle */
240     };
241 
242     union _FcitxConfigConstrain {
243         struct {
244             int min;
245             int max;
246         } integerConstrain;
247 
248         struct {
249             size_t maxLength;
250         } stringConstrain;
251 
252         struct {
253             boolean disallowNoModifer;
254             boolean allowModifierOnly;
255         } hotkeyConstrain;
256 
257         void* padding[10];
258     };
259 
260     /**
261      * Config option description v2
262      */
263     struct _FcitxConfigOptionDesc2 {
264         struct _FcitxConfigOptionDesc optionDesc;
265         boolean advance;
266         FcitxConfigConstrain constrain;
267         char* longDesc;
268         void* padding[16];
269     };
270 
271     /**
272      * Config Group Description, it describe a [Gruop] in config file
273      **/
274 
275     struct _FcitxConfigGroupDesc {
276         char *groupName; /**< Group Name */
277         FcitxConfigOptionDesc *optionsDesc; /**< Hash table for option description */
278         UT_hash_handle hh; /**< hash handle */
279     };
280 
281     /**
282      * Description of a config file
283      **/
284     struct _FcitxConfigFileDesc {
285         FcitxConfigGroupDesc *groupsDesc; /**< group description */
286         char* domain; /**< domain for translation */
287     };
288 
289     /**
290      * Config Option in config file, Key=Value entry
291      **/
292     struct _FcitxConfigOption {
293         char *optionName; /**< option name */
294         char *rawValue; /**< raw string value */
295         FcitxConfigValueType value; /**< value type */
296         FcitxSyncFilter filter; /**< filter function */
297         void *filterArg; /**< argument for filter function */
298         union {
299             FcitxConfigOptionDesc *optionDesc; /**< option description pointer */
300             FcitxConfigOptionDesc2 *optionDesc2; /**< option description pointer */
301         };
302         FcitxConfigOptionSubkey *subkey; /**< subkey which only used with I18NString */
303         UT_hash_handle hh; /**< hash handle */
304     } ;
305 
306     /**
307      * Config Option subkey in config file, Key[Subkey]=Value entry
308      **/
309     struct _FcitxConfigOptionSubkey {
310         char *subkeyName; /**< subkey name */
311         char *rawValue; /**< subkey raw value */
312         UT_hash_handle hh; /**< hash handle */
313     };
314 
315     /**
316      * Config group in config file, [Group] Entry
317      **/
318     struct _FcitxConfigGroup {
319         /**
320          * Group Name, unique in FcitxConfigFile
321          **/
322         char *groupName;
323         /**
324          * Group Description
325          **/
326         FcitxConfigGroupDesc *groupDesc;
327         /**
328          * Option store with a hash table
329          **/
330         FcitxConfigOption* options;
331         /**
332          * UTHash handler
333          **/
334         UT_hash_handle hh;
335     };
336 
337     /**
338      * declare the binding function
339      **/
340 #define CONFIG_BINDING_DECLARE(config_type) \
341     void config_type##ConfigBind(config_type* config, FcitxConfigFile* cfile, FcitxConfigFileDesc* cfdesc);
342 
343     /**
344      * declare the binding function, with extra argument
345      **/
346 #define CONFIG_BINDING_DECLARE_WITH_ARG(config_type, arg...) \
347     void config_type##ConfigBind(config_type* config, FcitxConfigFile* cfile, FcitxConfigFileDesc* cfdesc, arg);
348 
349     /**
350      * define the binding function
351      * each binding group for a config file will define a new function
352      * the structure is like: <br>
353      * CONFIG_BINDING_BEGIN <br>
354      * CONFIG_BINDING_REGISTER <br>
355      * .... <br>
356      * CONFIG_BINDING_REGISTER <br>
357      * CONFIG_BINDING_END
358      **/
359 #define CONFIG_BINDING_BEGIN(config_type) \
360     void config_type##ConfigBind(config_type* config, FcitxConfigFile* cfile, FcitxConfigFileDesc* cfdesc) { \
361         (void) cfdesc; \
362         FcitxGenericConfig *gconfig = (FcitxGenericConfig*) config; \
363         if (gconfig->configFile) { \
364             FcitxConfigFreeConfigFile(gconfig->configFile); \
365         } \
366         gconfig->configFile = cfile;
367 
368         /** register binding and call it with extra argument */
369 #define CONFIG_BINDING_BEGIN_WITH_ARG(config_type, arg...) \
370     void config_type##ConfigBind(config_type* config, FcitxConfigFile* cfile, FcitxConfigFileDesc* cfdesc, arg) { \
371         (void) cfdesc; \
372         FcitxGenericConfig *gconfig = (FcitxGenericConfig*) config; \
373         if (gconfig->configFile) { \
374             FcitxConfigFreeConfigFile(gconfig->configFile); \
375         } \
376         gconfig->configFile = cfile;
377     /**
378      * register a binding
379      **/
380 #define CONFIG_BINDING_REGISTER(g, o, var) \
381     do { \
382         FcitxConfigBindValue(cfile, g, o, &config->var, NULL, NULL); \
383     } while(0);
384 
385     /**
386      * register a binding with filter
387      **/
388 #define CONFIG_BINDING_REGISTER_WITH_FILTER(g, o, var, filter_func) \
389     do { \
390         FcitxConfigBindValue(cfile, g, o, &config->var, filter_func, NULL); \
391     } while(0);
392 
393     /**
394      * register a binding with filter and extra argument
395      **/
396 #define CONFIG_BINDING_REGISTER_WITH_FILTER_ARG(g, o, var, filter_func, arg) \
397     do { \
398         FcitxConfigBindValue(cfile, g, o, &config->var, filter_func, arg); \
399     } while(0);
400 
401     /**
402      * binding group end
403      **/
404 #define CONFIG_BINDING_END() }
405 
406     /**
407      * define a singleton function to load config file description
408      **/
409 #define CONFIG_DESC_DEFINE(funcname, path) \
410     FcitxConfigFileDesc *funcname() \
411     { \
412         static FcitxConfigFileDesc *configDesc = NULL; \
413         if (!configDesc) \
414         { \
415             FILE *tmpfp; \
416             tmpfp = FcitxXDGGetFileWithPrefix("configdesc", path, "r", NULL); \
417             if (tmpfp == NULL) \
418             { \
419                 FcitxLog(ERROR, "Load Config Description File %s Error, Please Check your install.", path); \
420                 return NULL; \
421             } \
422             configDesc = FcitxConfigParseConfigFileDescFp(tmpfp); \
423             fclose(tmpfp); \
424         } \
425         return configDesc; \
426     }
427 
428 #define CONFIG_DEFINE_LOAD_AND_SAVE(name, type, config_name) \
429 CONFIG_DESC_DEFINE(Get##name##Desc, config_name ".desc") \
430 void name##SaveConfig(type* _cfg) \
431 { \
432     FcitxConfigFileDesc* configDesc = Get##name##Desc(); \
433     FILE *fp = FcitxXDGGetFileUserWithPrefix("conf", config_name ".config", "w", NULL); \
434     FcitxConfigSaveConfigFileFp(fp, &_cfg->gconfig, configDesc); \
435     if (fp) \
436         fclose(fp); \
437 } \
438 boolean name##LoadConfig(type* _cfg) { \
439     FcitxConfigFileDesc* configDesc = Get##name##Desc(); \
440     if (configDesc == NULL) \
441         return false; \
442     \
443     FILE *fp; \
444     fp = FcitxXDGGetFileUserWithPrefix("conf", config_name ".config", "r", NULL); \
445     if (!fp) { \
446         if (errno == ENOENT) \
447             name##SaveConfig(_cfg); \
448     } \
449     FcitxConfigFile *cfile = FcitxConfigParseConfigFileFp(fp, configDesc); \
450     type##ConfigBind(_cfg, cfile, configDesc); \
451     FcitxConfigBindSync((FcitxGenericConfig*)_cfg); \
452     if (fp) \
453         fclose(fp); \
454     return true; \
455 } \
456 
457     /**
458      * parse a config file with file name.
459      * even the file cannot be read, or with wrong format,
460      * it will try to return a usable FcitxConfigFile (missing
461      * entry with defaul value).
462      *
463      * @param filename file name of a configfile
464      * @param cfdesc config file description
465      * @return FcitxConfigFile*
466      **/
467     FcitxConfigFile *FcitxConfigParseConfigFile(char *filename, FcitxConfigFileDesc* cfdesc);
468 
469     /**
470      * parse multi config file, the main difference
471      * between ParseConfigFile is that it parse multiple file
472      * and the duplicate entry will be overwritten with the
473      * file behind the previous one.
474      *
475      * @see ParseConfigFile
476      * @param filename filenames
477      * @param len len of filenames
478      * @param cfdesc config file description
479      * @return FcitxConfigFile*
480      **/
481     FcitxConfigFile *FcitxConfigParseMultiConfigFile(char **filename, int len, FcitxConfigFileDesc* cfdesc);
482 
483     /**
484      * same with ParseConfigFile, the input is FILE*
485      *
486      * @see ParseConfigFile
487      * @param fp file pointer
488      * @param fileDesc config file description
489      * @return FcitxConfigFile*
490      **/
491     FcitxConfigFile *FcitxConfigParseConfigFileFp(FILE* fp, FcitxConfigFileDesc* fileDesc);
492 
493     /**
494      * same with FcitxConfigParseMultiConfigFileFp, the input is array of FILE*
495      *
496      * @see FcitxConfigParseMultiConfigFileFp
497      * @param fp array of file pointers
498      * @param len length of fp
499      * @param fileDesc config file description
500      * @return FcitxConfigFile*
501      **/
502     FcitxConfigFile *FcitxConfigParseMultiConfigFileFp(FILE **fp, int len, FcitxConfigFileDesc* fileDesc);
503 
504     /**
505      * Check the raw FcitxConfigFile and try to fill the default value
506      *
507      * @param configFile config file
508      * @param fileDesc config file description
509      * @return boolean
510      **/
511     boolean FcitxConfigCheckConfigFile(FcitxConfigFile *configFile, FcitxConfigFileDesc* fileDesc);
512 
513     /**
514      * parse config file description from file
515      *
516      * @param filename filename
517      * @return FcitxConfigFileDesc*
518      **/
519     FcitxConfigFileDesc *FcitxConfigParseConfigFileDesc(char* filename);
520 
521     /**
522      * parse config file description from file pointer
523      *
524      * @see ParseConfigFileDesc
525      * @param fp file pointer
526      * @return FcitxConfigFileDesc*
527      **/
528     FcitxConfigFileDesc *FcitxConfigParseConfigFileDescFp(FILE* fp);
529 
530     /**
531      * internal raw file parse, it can merge the config to existing config file
532      *
533      * @param filename file
534      * @param reuse NULL or existing config file
535      * @return FcitxConfigFile*
536      **/
537     FcitxConfigFile* FcitxConfigParseIni(char* filename, FcitxConfigFile* reuse);
538 
539     /**
540      * internal raw file parse, it can merge the config to existing config file
541      *
542      * @see ParseIni
543      * @param fp file pointer
544      * @param reuse NULL or existing config file
545      * @return FcitxConfigFile*
546      **/
547     FcitxConfigFile* FcitxConfigParseIniFp(FILE* fp, FcitxConfigFile* reuse);
548 
549     /**
550      * free a config file
551      *
552      * @param cfile config file
553      * @return void
554      **/
555     void FcitxConfigFreeConfigFile(FcitxConfigFile* cfile);
556 
557     /**
558      * free a config file description
559      *
560      * @param cfdesc config file description
561      * @return void
562      **/
563     void FcitxConfigFreeConfigFileDesc(FcitxConfigFileDesc* cfdesc);
564 
565     /**
566      * free a config group
567      *
568      * @param group config group
569      * @return void
570      **/
571     void FcitxConfigFreeConfigGroup(FcitxConfigGroup *group);
572 
573     /**
574      * free a config group description
575      *
576      * @param cgdesc config group description
577      * @return void
578      **/
579     void FcitxConfigFreeConfigGroupDesc(FcitxConfigGroupDesc *cgdesc);
580 
581     /**
582      * free a config option
583      *
584      * @param option config option
585      * @return void
586      **/
587     void FcitxConfigFreeConfigOption(FcitxConfigOption *option);
588 
589     /**
590      * free a config option description
591      *
592      * @param codesc config option description
593      * @return void
594      **/
595     void FcitxConfigFreeConfigOptionDesc(FcitxConfigOptionDesc *codesc);
596 
597     /**
598      * Save config file to fp, it will do the Value2Raw sync
599      *
600      * @param filename file name
601      * @param cfile config
602      * @param cdesc config file description
603      * @return boolean
604      **/
605     boolean FcitxConfigSaveConfigFile(char *filename, FcitxGenericConfig *cfile, FcitxConfigFileDesc* cdesc);
606 
607     /**
608      * Save config file to fp
609      *
610      * @see SaveConfigFile
611      * @param fp file pointer
612      * @param cfile config
613      * @param cdesc config file dsecription
614      * @return boolean
615      **/
616     boolean FcitxConfigSaveConfigFileFp(FILE* fp, FcitxGenericConfig *cfile, FcitxConfigFileDesc* cdesc);
617 
618     /**
619      * sync a single value
620      *
621      * @param config config
622      * @param group config group
623      * @param option config option
624      * @param sync sync direction
625      * @return Svoid
626      **/
627     void FcitxConfigSyncValue(FcitxGenericConfig* config, FcitxConfigGroup* group, FcitxConfigOption* option, FcitxConfigSync sync);
628 
629     /**
630      * Get the binded value type
631      *
632      * @param config config
633      * @param group group name
634      * @param option option name
635      * @return FcitxConfigValueType
636      **/
637     FcitxConfigValueType FcitxConfigGetBindValue(FcitxGenericConfig *config, const char *group, const char* option);
638 
639     /**
640      * Get a option description from config file description, return NULL if not found.
641      *
642      * @param cfdesc config file description
643      * @param groupName group name
644      * @param optionName option name
645      * @return const FcitxConfigOptionDesc*
646      **/
647     const FcitxConfigOptionDesc* FcitxConfigDescGetOptionDesc(FcitxConfigFileDesc* cfdesc, const char* groupName, const char* optionName);
648 
649 
650     /**
651      * Get a option description from config file description, return NULL if not found.
652      *
653      * @param cfile config file
654      * @param groupName group name
655      * @param optionName option name
656      * @return const FcitxConfigOptionDesc*
657      *
658      * @since 4.1.2
659      **/
660     FcitxConfigOption* FcitxConfigFileGetOption(FcitxConfigFile* cfile, const char* groupName, const char* optionName);
661 
662 
663     /**
664      * Get the I18NString value from current locale
665      *
666      * @param option config option
667      * @return const char*
668      **/
669     const char* FcitxConfigOptionGetLocaleString(FcitxConfigOption* option);
670 
671     /**
672      * do the Raw2Value sync for config
673      *
674      * @param config config
675      * @return void
676      **/
677     void FcitxConfigBindSync(FcitxGenericConfig* config);
678 
679     /**
680      * reset a config to default value
681      *
682      * @param config config
683      * @return Svoid
684      **/
685     void FcitxConfigResetConfigToDefaultValue(FcitxGenericConfig* config);
686 
687     /**
688      * bind a value with a struct, normally you should use
689      * CONFIG_BINDING_ series macro, not directly this function.
690      *
691      * @param cfile config file
692      * @param groupName group name
693      * @param optionName option name
694      * @param var pointer to value
695      * @param filter filter function
696      * @param arg extra argument
697      * @return void
698      **/
699     void FcitxConfigBindValue(FcitxConfigFile* cfile, const char *groupName, const char *optionName, void* var, FcitxSyncFilter filter, void *arg);
700 
701     /**
702      * free a binded config struct with all related value
703      * @param config config
704      * @return void
705      */
706     void FcitxConfigFree(FcitxGenericConfig* config);
707 
708 #ifdef __cplusplus
709 }
710 
711 #endif
712 
713 #endif
714 
715 /**
716  * @}
717  */
718 
719 // kate: indent-mode cstyle; space-indent on; indent-width 0;
720