1 /*********************************************************************
2  *
3  * KasumiConfiguration.cxx
4  *
5  * Kasumi - a management tool for a private dictionary of anthy
6  *
7  * Copyright (C) 2004-2006 Takashi Nakamoto
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA  02110-1301, USA.
23  *
24 *********************************************************************/
25 
26 #include <cstdlib>
27 #include <iostream>
28 #include <fstream>
29 #include <map>
30 #include <list>
31 #include <getopt.h> /* for getopt_long() */
32 #include <anthy/dicutil.h>
33 
34 #include "KasumiConfiguration.hxx"
35 #include "KasumiException.hxx"
36 #include "KasumiString.hxx"
37 #include "KasumiWordType.hxx"
38 #include "intl.h"
39 
40 #ifdef HAVE_CONFIG_H
41 #include "config.h"
42 #endif
43 
44 using namespace std;
45 
46 // if you want to add a new configuration settings, do the following:
47 //  1. Set default value: Add a line to loadDefaultProperties() method
48 //     like;
49 //       config[string("NewKey")] = string("DefaultValue");
50 //  2. Add check routine for validating the setting in checkValidity method.
51 //     If it accepts only an integer value, it is the best to add the new key's
52 //     name to intValueKeyNames list like;
53 //       intValueKeyNames.push_back("NewKey");
54 //     Or if it is a shortcut key setting, add the new key's name to keyName
55 //     list like:
56 //       keyName.push_back("NewKey");
57 //  3. If the setting may be set by command line arguments, add some routines
58 //     to loadConfigurationFromArgument method
59 
KasumiConfiguration(int argc,char * argv[])60 KasumiConfiguration::KasumiConfiguration(int argc, char *argv[])
61   throw(KasumiException){
62 
63   try{
64     loadDefaultProperties();
65 
66     // ~/.kasumi must be encoded in EUC-JP
67 
68     char *home = getenv("HOME");
69     if(home == NULL){
70       throw KasumiException(string("Cannot find $HOME environment variable."),
71                             STDERR,
72                             KILL);
73     }
74 
75     ConfFileName = string(home) + "/.kasumi";
76 
77     loadConfigurationFile();
78   }catch(KasumiException e){
79     throw e;
80   }
81 
82   loadConfigurationFromArgument(argc, argv);
83   checkValidity();
84 }
85 
~KasumiConfiguration()86 KasumiConfiguration::~KasumiConfiguration(){
87   saveConfiguration();
88 }
89 
loadDefaultProperties()90 void KasumiConfiguration::loadDefaultProperties() throw(KasumiException){
91   char *home = getenv("HOME");
92   if(home == NULL){
93       throw KasumiException(string("Cannot find $HOME environment variable."),
94                             STDERR,
95 
96                             KILL);
97   }
98 
99   config[string("StartupMode")] = string("MANAGE");
100   config[string("DefaultFrequency")] = string("500");
101   config[string("MaxFrequency")] = string("1000");
102   config[string("MinFrequency")] = string("1");
103 //  config[string("QuitShortcutKey")] = string("Ctrl+Q");
104 //  config[string("StoreShortcutKey")] = string("Ctrl+S");
105 //  config[string("NewWordShortcutKey")] = string("Ctrl+N");
106 //  config[string("RemoveShortcutKey")] = string("Ctrl+R");
107 //  config[string("AddShortcutKey")] = string("Ctrl+A");
108 //  config[string("AddingModeShortcutKey")] = string("Ctrl+J");
109 //  config[string("ManageModeShortcutKey")] = string("Ctrl+M");
110   config[string("DefaultSpelling")] = string("");
111   config[string("DefaultSound")] = string("");
112   config[string("DefaultWordType")] = string("#T35");
113   config[string("DefaultAddingSpelling")] = string("");
114   config[string("DefaultAddingSound")] = string("");
115   config[string("DefaultAddingWordType")] = string("#T35");
116   //  config[string("DefaultWindowPosX")] = string("-1");
117   //  config[string("DefaultWindowPosY")] = string("-1");
118   config[string("ImportSelectedText")] = string("true");
119 #ifdef HAS_ANTHY_DICUTIL_SET_ENCODING
120   config[string("UseEUCJP")] = string("false");
121 #else // HAS_ANTHY_DICUTIL_SET_ENCODING
122   config[string("UseEUCJP")] = string("true");
123 #endif // HAS_ANTHY_DICUTIL_SET_ENCODING
124 }
125 
loadConfigurationFromArgument(int argc,char * argv[])126 void KasumiConfiguration::loadConfigurationFromArgument(int argc, char *argv[])
127   throw(KasumiException){
128   int option_index = 0;
129   static struct option long_options[] = {
130     {"help", no_argument, NULL, 'h'},
131     {"version", no_argument, NULL, 'v'},
132     {"add", no_argument, NULL, 'a'},
133     {"exclusive", no_argument, NULL, 'e'},
134     {"manage", no_argument, NULL, 'm'},
135     {"sound", required_argument, NULL, 's'},
136     {"spelling", required_argument, NULL, 't'},
137     {"wordclass", required_argument, NULL, 'w'},
138     //    {"x", required_argument, NULL, 'x'},
139     //    {"y", required_argument, NULL, 'y'},
140     {"import", no_argument, NULL, 'i'},
141     {"ignore", no_argument, NULL, 'I'},
142 #ifdef HAS_ANTHY_DICUTIL_SET_ENCODING
143     {"eucjp", no_argument, NULL, 'E'},
144 #endif // HAS_ANTHY_DICUTIL_SET_ENCODING
145     {0,0,0,0}
146   };
147 
148   string message;
149   int c;
150   while(1){
151     //    c = getopt_long(argc, argv, "hvamiIns:t:w:x:y:", long_options, &option_index);
152 #ifdef HAS_ANTHY_DICUTIL_SET_ENCODING
153     c = getopt_long(argc, argv, "hvaemiInEs:t:w:", long_options, &option_index);
154 #else // HAS_ANTHY_DICUTIL_SET_ENCODING
155     c = getopt_long(argc, argv, "hvaemiIns:t:w:", long_options, &option_index);
156 #endif // HAS_ANTHY_DICUTIL_SET_ENCODING
157     if(c == -1) break; // no more argument
158 
159     switch(c){
160     case 'h':
161       setPropertyValue(string("StartupMode"),string("HELP"));
162       break;
163     case 'v':
164       setPropertyValue(string("StartupMode"),string("VERSION"));
165       break;
166     case 'a':
167       setPropertyValue(string("StartupMode"),string("ADD"));
168       break;
169     case 'm':
170       setPropertyValue(string("StartupMode"),string("MANAGE"));
171       break;
172     case 'e':
173       setPropertyValue(string("StartupMode"),string("EXCLUSIVE"));
174       break;
175     case 's':
176       setPropertyValue(string("DefaultAddingSound"),string(optarg));
177       break;
178     case 't':
179       setPropertyValue(string("DefaultAddingSpelling"),string(optarg));
180       break;
181     case 'w':
182       setPropertyValue(string("DefaultAddingWordType"),string(optarg));
183       break;
184       //    case 'x':
185       //      setPropertyValue(string("DefaultWindowPosX"),string(optarg));
186       //      break;
187       //    case 'y':
188       //      setPropertyValue(string("DefaultWindowPosY"),string(optarg));
189       //      break;
190     case 'i':
191       setPropertyValue(string("ImportSelectedText"),string("true"));
192       break;
193     case 'I':
194       setPropertyValue(string("ImportSelectedText"),string("false"));
195       break;
196     case 'E':
197       setPropertyValue(string("UseEUCJP"),string("true"));
198       break;
199     case '?':
200     case ':':
201       message = string("Invalid argument error. Try '") + argv[0] +
202         string(" --help' for more information.");
203       throw KasumiException(message, STDERR, KILL);
204       break;
205     }
206   }
207 
208   if(optind < argc){
209     message = string("Found non-option argument '") + argv[optind] +
210       string("'. Try '") + argv[0] +
211       string(" --help' for more information.");
212     throw KasumiException(message, STDERR, KILL);
213   }
214 }
215 
216 
loadConfigurationFile()217 void KasumiConfiguration::loadConfigurationFile()
218   throw(KasumiException){
219 
220   int line = 0;
221   string Contents = string();
222   KasumiString Buffer;
223 
224   ifstream ConfFile(ConfFileName.c_str());
225 
226   if(!ConfFile.is_open()){
227     return; // do not load configuration file
228   }
229 
230   // analyze Kasumi Configuration file reading each line
231   while(getline(ConfFile, Buffer, '\n')){
232     line++;
233 
234     if(Buffer.isCommentLine()){
235       // commented line; nothing to do
236     }else if(Buffer.isEmptyLine()){
237       // empty line; nothing to do
238     }else if(Buffer.isKeyValLine()){
239       config[Buffer.getKey()] = Buffer.getVal();
240     }else{
241       // not classfied line; configuration file is invalid!
242       string message = ConfFileName + string(":") + int2str(line)
243         + string(": invalid entry in configuration file.");
244       throw KasumiException(message, STDERR, KILL);
245     }
246   }
247 }
248 
249 // ToDo: implement saveConfiguration method
saveConfiguration()250 void KasumiConfiguration::saveConfiguration()
251   throw(KasumiException){
252 
253 }
254 
checkValidity()255 void KasumiConfiguration::checkValidity()
256   throw(KasumiException){
257 
258   if(config[string("StartupMode")] != string("MANAGE") &&
259      config[string("StartupMode")] != string("ADD") &&
260      config[string("StartupMode")] != string("EXCLUSIVE") &&
261      config[string("StartupMode")] != string("HELP") &&
262      config[string("StartupMode")] != string("VERSION")){
263     string message("StartupMode variable must be \"MANAGE\", \"EXCLUSIVE\" or \"ADD\"");
264     throw KasumiException(message, STDERR, KILL);
265   }
266 
267   // check conrresponding settings being an integer
268   list<string> intValueKeyNames;
269 
270   intValueKeyNames.push_back(string("DefaultFrequency"));
271   intValueKeyNames.push_back(string("MaxFrequency"));
272   intValueKeyNames.push_back(string("MinFrequency"));
273   //  intValueKeyNames.push_back(string("DefaultWindowPosX"));
274   //  intValueKeyNames.push_back(string("DefaultWindowPosY"));
275 
276   while(!intValueKeyNames.empty()){
277     string keyName = intValueKeyNames.front();
278     intValueKeyNames.pop_front();
279 
280     if(!isInt(config[keyName])){
281       string message = keyName + string(" variable must be an integer");
282       throw KasumiException(message, STDERR, KILL);
283     }
284   }
285 
286   // check integer value are suitable
287   int def = str2int(config[string("DefaultFrequency")]);
288   int max = str2int(config[string("MaxFrequency")]);
289   int min = str2int(config[string("MinFrequency")]);
290   if(min < 1){
291     throw KasumiException(string("MinFrequency must be greater than 0"),
292                           STDERR, KILL);
293   }else if(max < min){
294     throw KasumiException(string("MinFrequency must not be greater than MaxFrequency."),
295                           STDERR, KILL);
296   }else if(def > max){
297     throw KasumiException(string("DefaultFrequency must not be greater than MaxFrequency"),
298                           STDERR, KILL);
299   }else if(def < min){
300     throw KasumiException(string("DefaultFrequency must not be less than MinFrequency"),
301                           STDERR, KILL);
302   }
303 
304   //  int x = str2int(config[string("DefaultWindowPosX")]);
305   //  int y = str2int(config[string("DefaultWindowPosY")]);
306   //  if(x < -1){
307   //    throw KasumiException(string("DefaultWindowPosX must be -1 or more"),
308   //                          STDERR, KILL);
309   //  }else if(y < -1){
310   //    throw KasumiException(string("DefaultWindowPosY must be -1 or more"),
311   //                          STDERR, KILL);
312   //  }
313 
314   // check key configurations
315   // throws exeption if there is an invalid key or duplication
316 //  map<string,string> registeredKey;
317 //  list<string> keyNames;
318 
319 //  keyNames.push_back(string("QuitShortcutKey"));
320 //  keyNames.push_back(string("StoreShortcutKey"));
321 //  keyNames.push_back(string("NewWordShortcutKey"));
322 //  keyNames.push_back(string("RemoveShortcutKey"));
323 //  keyNames.push_back(string("AddShortcutKey"));
324 //  keyNames.push_back(string("AddingModeShortcutKey"));
325 //  keyNames.push_back(string("ManageModeShortcutKey"));
326 
327 //  while(!keyNames.empty()){
328 //    string keyName = keyNames.front();
329 //    keyNames.pop_front();
330 //
331 //    string shortKey = config[string(keyName)];
332 //    if(!isValidShortcutKey(shortKey)){
333 //      string message = string("Invalid shortcut key configuration for ") +
334 //        keyName + string(": ") + shortKey;
335 //      throw KasumiException(message, STDERR, KILL);
336 //    }
337   //  if(registeredKey.find(shortKey) == registeredKey.end()){
338 //      registeredKey.insert(make_pair(shortKey,keyName));
339 //    }else{
340 //      string message = string("Failed to set ") + keyName + string(" variable; ") + shortKey + string(" has been already registered as ") + registeredKey[shortKey];
341 //      throw KasumiException(message, STDERR, KILL);
342 //    }
343 //  }
344 
345   // check WordType configuration
346   list<string> keyForWordType;
347   map<string,bool> validWordType;
348 
349   keyForWordType.push_back(string("DefaultWordType"));
350   keyForWordType.push_back(string("DefaultAddingWordType"));
351 
352   WordTypeList::iterator p = KasumiWordType::beginWordTypeList();
353   while(p != KasumiWordType::endWordTypeList())
354   {
355       validWordType.insert(make_pair((*p)->getCannaTab(),true));
356       p++;
357   }
358 
359   while(!keyForWordType.empty()){
360     string keyName = keyForWordType.front();
361     keyForWordType.pop_front();
362     string val = config[keyName];
363 
364     if(validWordType.find(val) == validWordType.end()){
365       string message = val + string(" is an invalid word type for ") + keyName;
366       throw KasumiException(message, STDERR, KILL);
367     }
368   }
369 
370   // check conrresponding settings being an boolean
371   list<string> booleanValueKeyNames;
372 
373   booleanValueKeyNames.push_back(string("ImportSelectedText"));
374 
375   while(!booleanValueKeyNames.empty()){
376     string keyName = booleanValueKeyNames.front();
377     booleanValueKeyNames.pop_front();
378 
379     if(config[keyName] != "true" && config[keyName] != "false"){
380       throw KasumiException(keyName + string(" variable must be a boolean"),
381                             STDERR, KILL);
382     }
383   }
384 
385   // no check for:
386   //  DefaultSpelling
387   //  DefaultSound
388   //  DefaultAddingSpelling
389   //  DefaultAddingSound
390   // ToDo: confirm default sounds do not have invalid character
391 }
392 
setPropertyValue(const string & name,const string & value)393 void KasumiConfiguration::setPropertyValue(const string &name, const string &value){
394   map<string,string>::iterator p;
395 
396   p = config.find(name);
397 
398   if(p == config.end()){
399     cerr << "error: you cannot set " << name << " property." << endl;
400     exit(1);
401   }
402 
403   config[name] = value;
404 }
405 
getPropertyValue(const string & name)406 string KasumiConfiguration::getPropertyValue(const string &name){
407   map<string,string>::iterator p;
408 
409   p = config.find(name);
410 
411   if(p == config.end()){
412     cerr << "error: " << name << " property has not been set yet." << endl;
413     exit(1);
414   }
415 
416   return p->second;
417 }
418 
getPropertyValueByInt(const string & name)419 int KasumiConfiguration::getPropertyValueByInt(const string &name){
420   map<string,string>::iterator p;
421 
422   p = config.find(name);
423 
424   if(p == config.end()){
425     cerr << "error: " << name << " property has not been set yet." << endl;
426     exit(1);
427   }
428 
429   return str2int(p->second);
430 }
431 
getPropertyValueByBool(const string & name)432 bool KasumiConfiguration::getPropertyValueByBool(const string &name){
433   map<string,string>::iterator p;
434 
435   p = config.find(name);
436   if(p == config.end()){
437     cerr << "error: " << name << " property has not been set yet." << endl;
438     exit(1);
439   }
440 
441   if(p->second == "true"){
442     return true;
443   } else{
444     return false;
445   }
446 }
447 
448 /*
449 bool isValidShortcutKey(const string &key){
450   string::size_type i;
451 
452   i = key.find("+",0);
453   string shortkey = key.substr(i+1);
454   if(shortkey.length() != 1){
455     return false;
456   }
457   char c = shortkey.c_str()[0];
458   if((c < 'A' || c > 'Z') && (c < '0' || c > '9')){
459     return false;
460   }
461 
462   i = key.find("+",0);
463   if(i == key.npos){
464     return true;
465   }
466   string mask = key.substr(0,i);
467   if(mask == "Ctrl"){
468     return true;
469   }else if(mask == "Alt"){
470     return true;
471   }
472 
473   return false;
474 }
475 */
476