1 /*
2   FXiTe - The Free eXtensIble Text Editor
3   Copyright (c) 2009-2012 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4 
5   This program is free software; you can redistribute it and/or modify it
6   under the terms of the GNU General Public License version 3 as
7   published by the Free Software Foundation.
8 
9   This software is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 
20 #include <cctype>
21 #include <cerrno>
22 #include <fx.h>
23 #include <Scintilla.h>
24 
25 #include "compat.h"
26 #include "lang.h"
27 #include "search.h"
28 #include "appwin_pub.h"
29 #include "menuspec.h"
30 
31 
32 #include "intl.h"
33 #include "prefs.h"
34 
35 
36 #ifdef WIN32
37 # define DEFAULT_EOL_FORMAT   SC_EOL_CRLF
38 # define STYLE_FILE "styles.ini"
39 #else
40 # define STYLE_FILE "styles"
41 # define  DEFAULT_EOL_FORMAT   SC_EOL_LF
42 #endif
43 
44 
45 FXDEFMAP(Settings) SettingsMap[]={
46   FXMAPFUNCS(SEL_COMMAND,Settings::ID_TOGGLE_SMART_HOME,Settings::ID_SET_AUTO_INDENT, Settings::onChangeSetting)
47 };
48 
49 FXIMPLEMENT(Settings, FXObject, SettingsMap, ARRAYNUMBER(SettingsMap));
50 
51 #define LIMIT_RANGE(n,lo,hi) if (n<=lo) { n=lo; } else { if (n>=hi) { n=hi; } }
52 
53 #define SHELL_COMMAND "/bin/sh -c"
54 
55 #define FILE_FILTERS _("\
56 All Files (*)|\
57 C/C++ files (*.[ch],*.[ch]pp,*.[ch]xx,*.[CH],*.cc,*.hh)|\
58 Script files (*.sh,*.lua,*.rb,*.pl,*.py)|\
59 Config files (config*,*cfg,*conf,*.ini,*rc,.*)|\
60 Makefiles ([Mm]akefile*,*.mk,CMake*,*.cmake*,Jam*)|\
61 Web files (*html,*.htm,*.php*)|\
62 Text files (*.txt)|\
63 ")
64 
65 static const char* default_file_filters = FILE_FILTERS;
66 
67 
68 class EditorFontDlg: public FXFontDialog {
69 private:
70   FXSpinner*ascent;
71   FXSpinner*descent;
72   class EditorFontSel: public FXFontSelector {
73     public:
ActionArea()74     FXHorizontalFrame* ActionArea() {
75       return static_cast<FXHorizontalFrame*>(accept->getParent());
76     }
77   };
78 public:
EditorFontDlg(FXWindow * o)79   EditorFontDlg(FXWindow*o):FXFontDialog(o, _("Select Font"), 0) {
80     FXHorizontalFrame*frm=((EditorFontSel*)fontbox)->ActionArea();
81     (new FXLabel(frm, _("Line Spacing:\n(in pixels)")))->setPadTop(0);
82     new FXLabel(frm, _("  Above:"));
83     ascent=new FXSpinner(frm,2,NULL,0,SPIN_NORMAL|FRAME_SUNKEN|FRAME_THICK);
84     new FXLabel(frm, _("  Below:"));
85     descent=new FXSpinner(frm,2,NULL,0,SPIN_NORMAL|FRAME_SUNKEN|FRAME_THICK);
86     ascent->setRange(0,16);
87     descent->setRange(0,16);
88   }
getAscent()89   FXint getAscent() { return ascent->getValue(); }
getDescent()90   FXint getDescent() { return descent->getValue(); }
setAscent(FXint a)91   void setAscent(FXint a) { ascent->setValue(a); }
setDescent(FXint d)92   void setDescent(FXint d) { descent->setValue(d); }
93 };
94 
95 
96 
97 
onChangeSetting(FXObject * o,FXSelector sel,void * p)98 long Settings::onChangeSetting(FXObject*o, FXSelector sel, void*p)
99 {
100   switch FXSELID(sel) {
101     case ID_TOGGLE_SMART_HOME:  { SmartHome = !SmartHome; break; }
102     case ID_TOGGLE_WRAP_AWARE:  { WrapAwareHomeEnd = !WrapAwareHomeEnd; break; }
103     case ID_TOGGLE_USE_TABS:    {
104       FXSpinner*spin=(FXSpinner*)(((FXCheckButton*)o)->getUserData());
105       UseTabs = (bool)((FXival)p);
106       if (UseTabs) {
107         spin->disable();
108         spin->getNext()->disable();
109         spin->setTextColor(spin->getApp()->getBaseColor());
110       } else {
111         spin->enable();
112         spin->getNext()->enable();
113         spin->setTextColor(spin->getApp()->getForeColor());
114       }
115       break;
116     }
117     case ID_SET_BRACE_MATCHING: {
118       BraceMatch = (FXival)p;
119       LIMIT_RANGE(BraceMatch,BRACEMATCH_NONE,BRACEMATCH_AFTER);
120       break;
121     }
122     case ID_TOGGLE_ASK_CLOSE_MULTI_MENU: { PromptCloseMultiMenu = !PromptCloseMultiMenu; break; }
123     case ID_TOGGLE_ASK_CLOSE_MULTI_EXIT: { PromptCloseMultiExit = !PromptCloseMultiExit; break; }
124     case ID_TOGGLE_WATCH_EXTERN:    { WatchExternChanges = !WatchExternChanges; break; }
125     case ID_TOGGLE_SMOOTH_SCROLL:   { SmoothScroll  = !SmoothScroll;  break; }
126     case ID_TOGGLE_SEARCH_VERBOSE:  { SearchVerbose = !SearchVerbose; break; }
127     case ID_TOGGLE_CARET_PAST_EOL:  { CaretPastEOL  = !CaretPastEOL;  break; }
128     case ID_TOGGLE_VIEW_WHITESPACE_EOL: { WhitespaceShowsEOL = !WhitespaceShowsEOL; break; }
129     case ID_TOGGLE_ASCII_DEFAULT: { DefaultToAscii = !DefaultToAscii; break; }
130     case ID_TOGGLE_SBCS_DEFAULT: { DefaultToSbcs = !DefaultToSbcs; break; }
131     case ID_TOGGLE_WORD_WRAP: { WordWrap = !WordWrap; break; }
132     case ID_TOGGLE_WRAP_TOOLBAR: {
133       WrapToolbar = !WrapToolbar;
134       break;
135     }
136     case ID_TOGGLE_AUTOSAVE:        {
137       FXWindow*w=(FXWindow*)o;
138       w=(FXWindow*)w->getUserData();
139       Autosave = !Autosave;
140       if (Autosave) { w->enable(); } else { w->disable(); }
141       break;
142     }
143     case ID_SAVE_ON_FILTER_SEL: { SaveBeforeFilterSel = !SaveBeforeFilterSel; break; }
144     case ID_SAVE_ON_INS_CMD:    { SaveBeforeInsCmd    = !SaveBeforeInsCmd;    break; }
145     case ID_SAVE_ON_EXEC_CMD:   { SaveBeforeExecCmd   = !SaveBeforeExecCmd;   break; }
146     case ID_CHOOSE_FONT: {
147       EditorFontDlg dlg(((FXWindow*)o)->getShell());
148       dlg.setAscent(FontAscent);
149       dlg.setDescent(FontDescent);
150 #ifdef WIN32 // Windows font dialog is empty, unless setwidth is zero.
151       FXushort setwidth=fontdesc.setwidth;
152       fontdesc.setwidth=0;
153 #endif
154       SetDialogFromFont(dlg,fontdesc);
155       if (dlg.execute(PLACEMENT_SCREEN)) {
156         SetFontFromDialog(fontdesc,dlg);
157         FontName=(FXchar*)(fontdesc.face);
158         FontSize=fontdesc.size;
159         FontAscent=dlg.getAscent();
160         FontDescent=dlg.getDescent();
161       }
162 #ifdef WIN32
163       else { fontdesc.setwidth=setwidth; }
164 #endif
165       break;
166     }
167     case ID_SET_MAX_FILES: {
168       FXTextField*tf=(FXTextField*)o;
169       MaxFiles=FXIntVal(tf->getText(),10);
170       LIMIT_RANGE(MaxFiles,1,999);
171       char maxfiles[8]="\0\0\0\0\0\0\0";
172       snprintf(maxfiles, sizeof(maxfiles)-1, "%d", MaxFiles);
173       tf->setText(maxfiles);
174       break;
175     }
176     case ID_SET_TAB_WIDTH: {
177       FXSpinner*spin=(FXSpinner*)o;
178       TabWidth=spin->getValue();
179       LIMIT_RANGE(TabWidth,1,16);
180       break;
181     }
182     case ID_SET_TAB_WIDTH_FOR_LANG: {
183       FXSpinner*spin=(FXSpinner*)o;
184       LangStyle*ls=(LangStyle*)spin->getUserData();
185       ls->tabwidth=spin->getValue();
186       LIMIT_RANGE(ls->tabwidth,0,16);
187       break;
188     }
189     case ID_SET_INDENT_WIDTH: {
190       FXSpinner*spin=(FXSpinner*)o;
191       IndentWidth=spin->getValue();
192       LIMIT_RANGE(IndentWidth,1,16);
193       break;
194     }
195     case ID_SET_CARET_WIDTH: {
196       FXSpinner*spin=(FXSpinner*)o;
197       CaretWidth=spin->getValue();
198       LIMIT_RANGE(CaretWidth,1,3);
199       break;
200     }
201     case ID_SET_WHEEL_LINES: {
202       FXSpinner*spin=(FXSpinner*)o;
203       WheelLines=spin->getValue();
204       LIMIT_RANGE(WheelLines,1,32);
205       break;
206     }
207     case ID_SET_TAB_TITLE_MAX_WIDTH: {
208       FXSpinner*spin=(FXSpinner*)o;
209       TabTitleMaxWidth=spin->getValue();
210       LIMIT_RANGE(TabTitleMaxWidth,0,ScreenWidth);
211     }
212     case ID_SET_SEARCH_WRAP: {
213       SearchWrap=(FXival)p;
214       LIMIT_RANGE(SearchWrap,SEARCH_WRAP_NEVER,SEARCH_WRAP_ASK);
215       break;
216     }
217     case ID_SET_SEARCH_GUI: {
218       SearchGui=(FXival)p;
219       LIMIT_RANGE(SearchGui,SEARCH_GUI_ABOVE,SEARCH_GUI_FLOAT);
220       break;
221     }
222 
223     case ID_SET_AUTO_INDENT: {
224       AutoIndent=(FXival)p;
225       LIMIT_RANGE(AutoIndent,AUTO_INDENT_NONE,AUTO_INDENT_SMART);
226       break;
227     }
228 
229     case ID_SET_SPLIT_VIEW: {
230       SplitView=(FXival)p;
231       LIMIT_RANGE(SplitView,SPLIT_NONE,SPLIT_BESIDE);
232       break;
233     }
234     case ID_SET_KEEP_FILE_FILTER: {
235       KeepFileFilter=(FXival)p;
236       LIMIT_RANGE(KeepFileFilter,REMEMBER_NEVER,REMEMBER_ALWAYS);
237       break;
238     }
239     case ID_SET_RIGHT_EDGE: {
240       FXSpinner*spin=(FXSpinner*)o;
241       RightEdgeColumn=spin->getValue();
242       LIMIT_RANGE(RightEdgeColumn,1,1024);
243       break;
244     }
245     case ID_SET_SHELL_CMD: {
246       FXTextField*tf=(FXTextField*)o;
247       ShellCommand=tf->getText().text();
248       break;
249     }
250     case ID_SET_AUTOSAVE_INT: {
251       FXSpinner*spin=(FXSpinner*)o;
252       AutosaveInterval=spin->getValue();
253       LIMIT_RANGE(AutosaveInterval,15,900);
254       break;
255     }
256     case ID_SET_SEARCH_OPTS: {
257       SearchOptions=(FXival)p;
258       break;
259     }
260     case ID_SET_FILETYPES: {
261       FXInputDialog*dlg=(FXInputDialog*)o;
262       LangStyle* ls=(LangStyle*)dlg->getUserData();
263       if (ls) {
264         if (ls->mask) {
265           if (strcmp(ls->mask,dlg->getText().text())==0) { break; }
266           if ( ls->mallocs & (1<<30) ) { free(ls->mask); }
267         }
268         ls->mask=strdup(dlg->getText().text());
269       }
270       break;
271     }
272     case ID_SET_SHABANGS: {
273       FXInputDialog*dlg=(FXInputDialog*)o;
274       LangStyle* ls=(LangStyle*)dlg->getUserData();
275       if (ls) {
276         if (ls->apps) {
277           if (strcmp(ls->apps,dlg->getText().text())==0) { break; }
278           if ( ls->mallocs & (1<<29) ) { free(ls->apps); }
279         }
280         ls->apps=strdup(dlg->getText().text());
281       }
282       break;
283     }
284     case ID_SET_FILE_FORMAT: {
285       DefaultFileFormat=(FXival)p;
286       LIMIT_RANGE(DefaultFileFormat,0,2);
287       break;
288     }
289     case ID_SET_TOOLBAR_BTN_SIZE: {
290       ToolbarButtonSize=(FXival)p;
291       LIMIT_RANGE(ToolbarButtonSize,0,2);
292       break;
293     }
294   }
295   return 1;
296 }
297 
298 
299 
300 static StyleDef GlobalStyle[] = {
301   { "default",     STYLE_DEFAULT,     "#000000", "#ffffff", Normal },
302   { "linenumber",  STYLE_LINENUMBER,  "#0000c0", "#d0d0d0", Normal },
303   { "bracelight",  STYLE_BRACELIGHT,  "#ffff00", "#00d000", Bold  },
304   { "bracebad",    STYLE_BRACEBAD,    "#ffff00", "#ff0000", Bold  },
305   { "controlchar", STYLE_CONTROLCHAR, "#0000c0", "#d0d0d0", Normal },
306   { "indentguide", STYLE_INDENTGUIDE, "#0000c0", "#d0d0d0", Normal },
307   { "calltip",     STYLE_CALLTIP,     "#808000", "#e0e0d0", Normal },
308   { NULL,          0,                        "",        "", Normal }
309 };
310 
311 
312 static StyleDef WhiteSpaceStyle =  { "whitespace",  0,     "#000000", "#ffffdd", Normal };
313 static StyleDef CaretLineStyle =   { "caretline",   0,     "#000000", "#f8f8f8", Normal };
314 static StyleDef RightMarginStyle = { "rightmargin", 0,     "#000000", "#ff0000", Normal };
315 static StyleDef SelectionStyle =   { "selection",   0,     "#c0c0c0", "#c0c0c0", Normal };
316 static StyleDef CaretStyle =       { "caret",       0,     "#000000", "#000000", Normal };
317 
318 
319 
CaretFG()320 const FXchar* Settings::CaretFG()       { return CaretStyle.fg; }
CaretLineBG()321 const FXchar* Settings::CaretLineBG()   { return CaretLineStyle.bg; }
RightMarginBG()322 const FXchar* Settings::RightMarginBG() { return RightMarginStyle.bg; }
WhiteSpaceBG()323 const FXchar* Settings::WhiteSpaceBG()  { return WhiteSpaceStyle.bg; }
WhiteSpaceFG()324 const FXchar* Settings::WhiteSpaceFG()  { return WhiteSpaceStyle.fg; }
SelectionBG()325 const FXchar* Settings::SelectionBG()   { return SelectionStyle.bg; }
326 
327 
328 
329 static ErrorPattern DefaultErrPats[8] = {
330 #ifdef __minix
331   { "^\"([^\\s\"]+)\",[^(]+(\\d+)",  "MINIX ACK" },
332 #endif
333   { "([^\\s:]+):(\\d+)",             "GCC" },
334   { "([^\\s(]+)\\((\\d+)",           "FreePascal" },
335   {"",""},
336   {"",""},
337   {"",""},
338   {"",""},
339   {"",""},
340 #ifndef __minix
341   {"",""},
342 #endif
343 };
344 
345 #define MAX_ERROR_PATTERNS ARRAYNUMBER(DefaultErrPats)
346 
347 static ErrorPattern UserErrorPatterns[MAX_ERROR_PATTERNS];
348 
349 static const char* errpats_sect="ErrorPatterns";
350 
ErrorPatterns()351 ErrorPattern* Settings::ErrorPatterns() { return UserErrorPatterns; }
352 
DefaultErrorPatterns()353 ErrorPattern* Settings::DefaultErrorPatterns() { return DefaultErrPats; }
354 
MaxErrorPatterns()355 int Settings::MaxErrorPatterns() { return (MAX_ERROR_PATTERNS); }
356 
357 
ReadErrorPatterns(FXSettings * reg)358 static void ReadErrorPatterns(FXSettings*reg)
359 {
360   char pat_key[]="Pattern_\0\0";
361   char id_key[]="Comment_\0\0";
362   memset(UserErrorPatterns,0,sizeof(ErrorPattern)*MAX_ERROR_PATTERNS);
363   if (reg->existingSection(errpats_sect)) {
364     FXRex *rx=new FXRex();
365     for (FXuint n=0;n<MAX_ERROR_PATTERNS; n++) {
366       pat_key[8] = id_key[8] = n+65;
367       FXString pat=reg->readStringEntry(errpats_sect,pat_key,"");
368       FXString id=reg->readStringEntry(errpats_sect,id_key,"");
369       if (!(id.empty()||pat.empty())) {
370         FXRexError err=rx->parse(pat,REX_CAPTURE|REX_SYNTAX);
371         if (err!=REGERR_OK) {
372           fxwarning("%s: %s\n", _("Error parsing regular expression"), FXRex::getError(err));
373         }
374         strncpy(UserErrorPatterns[n].pat, pat.text(), sizeof(UserErrorPatterns[n].pat)-1);
375         strncpy(UserErrorPatterns[n].id,  id.text(),  sizeof(UserErrorPatterns[n].id)-1);
376       }
377     }
378     delete rx;
379   } else {
380     memcpy(UserErrorPatterns,DefaultErrPats,sizeof(ErrorPattern)*MAX_ERROR_PATTERNS);
381   }
382 }
383 
384 
385 
SaveErrorPatterns(FXSettings * reg)386 static void SaveErrorPatterns(FXSettings*reg)
387 {
388   char pat_key[]="Pattern_\0\0";
389   char id_key[]="Comment_\0\0";
390   reg->deleteSection(errpats_sect);
391   for (int n=0;n<=9&&UserErrorPatterns[n].pat[0]; n++) {
392     pat_key[8] = id_key[8] = n+65;
393     reg->writeStringEntry(errpats_sect,pat_key,UserErrorPatterns[n].pat);
394     reg->writeStringEntry(errpats_sect,id_key,UserErrorPatterns[n].id);
395   }
396 }
397 
398 
399 
ErrorPatternCount()400 int Settings::ErrorPatternCount()
401 {
402   FXuint rv=0;
403   for (rv=0; rv<MAX_ERROR_PATTERNS && UserErrorPatterns[rv].pat[0]; rv++) { }
404   return rv;
405 }
406 
407 
408 
409 static const char* sysincs_sect="SystemIncludes";
410 
411 #define MAX_SYSINC_PATHS 255
412 
413 
414 static const FXchar* DefaultSysIncs =
415   "/usr/include\n"
416 #ifdef __minix
417   "/usr/pkg/include\n"
418   "/usr/pkg/X11R6\n"
419 #endif
420   "/usr/local/include\n"
421 ;
422 
423 
424 static FXString SysIncs = FXString::null;
425 
426 
427 
defaultSystemIncludePaths()428 const FXString Settings::defaultSystemIncludePaths()
429 {
430   return DefaultSysIncs;
431 }
432 
433 
434 
SystemIncludePaths()435 const FXString Settings::SystemIncludePaths()
436 {
437   return SysIncs;
438 }
439 
440 
441 
SystemIncludePaths(const FXString paths)442 void Settings::SystemIncludePaths(const FXString paths)
443 {
444   SysIncs=paths;
445 }
446 
447 
448 
SaveSysIncPaths(FXSettings * reg)449 static void SaveSysIncPaths(FXSettings*reg)
450 {
451   reg->deleteSection(sysincs_sect);
452   int n=SysIncs.contains('\n');
453   if (n>MAX_SYSINC_PATHS) { n=MAX_SYSINC_PATHS; }
454   for (int i=0; i<n; i++) {
455     const FXString value=SysIncs.section('\n',i);
456     if (!value.empty()) {
457       char key[16];
458       snprintf(key, sizeof(key)-1 ,"Path_%d", i+1);
459       reg->writeStringEntry(sysincs_sect,key,value.text());
460     }
461   }
462 }
463 
464 
465 
ReadSysIncPaths(FXSettings * reg)466 static void ReadSysIncPaths(FXSettings*reg)
467 {
468   SysIncs=FXString::null;
469   if (reg->existingSection(sysincs_sect)) {
470     for (int i=0; i<MAX_SYSINC_PATHS; i++) {
471       char key[16];
472       snprintf(key, sizeof(key)-1 ,"Path_%d", i+1);
473       if (reg->existingEntry(sysincs_sect,key)) {
474         const char* value=reg->readStringEntry(sysincs_sect,key,NULL);
475         if (value && *value) {
476           SysIncs+=value;
477           SysIncs+='\n';
478         }
479       }
480     }
481   }
482   if (SysIncs.empty()) { SysIncs=DefaultSysIncs; }
483 }
484 
485 
486 
IsColor(const char * clr)487 static FXbool IsColor(const char*clr)
488 {
489   const char*p;
490   if ( (clr) && (clr[0]=='#') && (strlen(clr)==7) ) {
491     for (p=clr+1; *p; p++) { if (!isxdigit(*p)) { return false; } }
492     return true;
493   } else { return false; }
494 }
495 
496 
497 
SaveStyle(FXSettings * reg,const char * section,StyleDef * style)498 static void SaveStyle(FXSettings*reg, const char*section, StyleDef *style)
499 {
500   FXString tmp="";
501   tmp.format("%s,%s", style->fg[0]?style->fg:"default",style->bg[0]?style->bg:"default");
502   if (style->style==Normal) {
503     tmp.append(",Normal");
504   } else {
505     if (style->style & Bold) {
506       tmp.append(",Bold");
507     }
508     if (style->style & Italic) {
509       tmp.append(",Italic");
510     }
511     if (style->style & Underline) {
512       tmp.append(",Underline");
513     }
514     if (style->style & EOLFill) {
515       tmp.append(",EOLFill");
516     }
517   }
518   reg->writeStringEntry(section,style->key,tmp.text());
519 }
520 
521 
522 
SaveStyles(FXSettings * reg,const char * section,StyleDef * style)523 static void SaveStyles(FXSettings*reg, const char*section, StyleDef *style)
524 {
525   int i;
526   for (i=0; style[i].key; i++) {
527     SaveStyle(reg,section,&style[i]);
528   }
529 }
530 
531 
532 
ParseStyle(FXSettings * reg,const char * section,StyleDef * style)533 static void ParseStyle(FXSettings*reg, const char*section, StyleDef *style)
534 {
535   if (style->key) {
536     FXString tmp=reg->readStringEntry(section, style->key, "");
537     tmp.lower();
538     tmp.substitute('\t', ' ', true);
539     tmp.trim();
540     tmp.simplify();
541     tmp.substitute(" ", "", true);
542     if (!tmp.empty()) {
543       char buf[256];
544       char *fg=buf;
545       char *bg=NULL;
546       char *fs=NULL;
547       int flags=Normal;
548       strncpy(buf, tmp.text(), sizeof(buf)-1);
549       bg=strchr(buf,',');
550       if (bg) {
551         *bg='\0';
552         bg++;
553         fs=strchr(bg,',');
554         if (fs) {
555           *fs='\0';
556           fs++;
557           if (strstr(fs,"italic")) { flags|=Italic; }
558           if (strstr(fs,"bold")) { flags|=Bold; }
559           if (strstr(fs,"underline")) { flags|=Underline; }
560           if (strstr(fs,"eolfill")) { flags|=EOLFill; }
561         }
562         if (IsColor(bg)) { strncpy(style->bg, bg, 7); }
563       }
564       if (IsColor(fg)) { strncpy(style->fg, fg, 7); }
565       style->style=(SciDocFontStyle)(flags);
566     }
567   }
568 }
569 
570 
571 
ParseStyles(FXSettings * reg,const char * section,StyleDef * style)572 static void ParseStyles(FXSettings*reg, const char*section, StyleDef *style)
573 {
574   int i;
575   for (i=0; style[i].key; i++) {
576     ParseStyle(reg,section,&style[i]);
577   }
578 }
579 
580 
581 
instance()582 Settings* Settings::instance()         { return (Settings*)SettingsBase::instance(); }
globalStyle()583 StyleDef* Settings::globalStyle()      { return GlobalStyle; }
whitespaceStyle()584 StyleDef* Settings::whitespaceStyle()  { return &WhiteSpaceStyle; }
caretlineStyle()585 StyleDef* Settings::caretlineStyle()   { return &CaretLineStyle; }
rightmarginStyle()586 StyleDef* Settings::rightmarginStyle() { return &RightMarginStyle; }
selectionStyle()587 StyleDef* Settings::selectionStyle()   { return &SelectionStyle; }
caretStyle()588 StyleDef* Settings::caretStyle()       { return &CaretStyle; }
defaultFileFilters()589 const FXchar *Settings::defaultFileFilters() { return default_file_filters; }
590 
591 
592 
593 // List of possible font names - we check if any system
594 // font name *begins* with any of these phrases...
595 static const char* tryfonts[] = {
596   "Liberation Mono ",
597   "DejaVu Sans Mono ",
598   "FreeMono ",
599   "Courier New ",
600   "Nimbus Mono ",
601   "Courier ",
602   "LucidaTypewriter ",
603   "Fixed ",
604   "Terminal ",
605   "Clean ",
606   "Anonymous ",
607   NULL
608 };
609 
610 
611 // Try to guess at a suitable font - we only do this when
612 // there is no "FontName" entry in the registry.
FindFont(FXString & FontName)613 static void FindFont(FXString &FontName) {
614   FXFontDesc*fonts=NULL;
615   FXuint numfonts=0;
616   if (FXFont::listFonts(fonts,numfonts,FXString::null)) {
617     // First, try each of the items in our "tryfonts" list...
618     for (const char**tryfont=tryfonts; *tryfont; tryfont++) {
619       for (FXuint i=0; i<numfonts; i++) {
620         FXFontDesc fd=fonts[i];
621         if (strncasecmp(fd.face, *tryfont, strlen(*tryfont))==0) {
622            FontName=fd.face;
623            freeElms(fonts);
624            return;
625         }
626       }
627     } // keep looking...
628     // Try to grab the first thing that is fixed-width and scalable.
629     for (FXuint i=0; i<numfonts; i++) {
630       FXFontDesc fd=fonts[i];
631       if ((fd.flags&FXFont::Fixed) && (fd.flags&FXFont::Scalable)) {
632         FontName=fd.face;
633         freeElms(fonts);
634         return;
635       }
636     } // keep looking...
637     // Try to grab the first thing that is fixed-width and reasonably sized.
638     for (FXuint i=0; i<numfonts; i++) {
639       FXFontDesc fd=fonts[i];
640       if ((fd.flags&FXFont::Fixed)&&(fd.size>=100)&&(fd.size<200)) {
641         FontName=fd.face;
642         freeElms(fonts);
643         return;
644       }
645     }
646     freeElms(fonts);
647   }
648   FontName="fixed"; // I give up!
649 }
650 
651 
652 /*
653   Break some of the preference settings up into to categories, each category represents a
654   separate section header in the config file. Note that in order for the Read/Write macros
655   to work correctly, these strings must *exactly* match their respective variable names!
656 */
657 
658 static const char* view_keys[] = {
659   "ShowStatusBar",
660   "ShowLineNumbers",
661   "ShowToolbar",
662   "ShowWhiteSpace",
663   "ShowOutputPane",
664   "InvertColors",
665   "ShowRightEdge",
666   "ShowIndentGuides",
667   "DocTabsPacked",
668   "ZoomFactor",
669   "DocTabPosition",
670   "ShowCaretLine",
671   NULL
672 };
673 
674 static const char* edit_keys[] = {
675   "SmartHome",
676   "WrapAwareHomeEnd",
677   "AutoIndent",
678   "BraceMatch",
679   "UseTabs",
680   "CaretPastEOL",
681   "WhitespaceShowsEOL",
682   "SearchWrap",
683   "SearchVerbose",
684   "SearchOptions",
685   "SearchGui",
686   "TabWidth",
687   "IndentWidth",
688   "CaretWidth",
689   "SmoothScroll",
690   "WheelLines",
691   "FontName",
692   "FontSize",
693   "fontdesc",
694   "FontAscent",
695   "FontDescent",
696   NULL
697 };
698 
699 static const char* geom_keys[] = {
700   "Maximize",
701   "Left",
702   "Top",
703   "Width",
704   "Height",
705   "OutputPaneHeight",
706   "placement",
707   "LastFocused",
708   "TabTitleMaxWidth",
709   NULL
710 };
711 
712 static const char* main_keys[] = {
713   "PromptCloseMultiMenu",
714   "PromptCloseMultiExit",
715   "WatchExternChanges",
716   "Autosave",
717   "SaveBeforeFilterSel",
718   "SaveBeforeInsCmd",
719   "SaveBeforeExecCmd",
720   "SplitView",
721   "FileFilters",
722   "RightEdgeColumn",
723   "ShellCommand",
724   "MaxFiles",
725   "AutosaveInterval",
726   "DefaultFileFormat",
727   "DefaultToAscii",
728   "DefaultToSbcs",
729   "KeepFileFilter",
730   "FileFilterIndex",
731   "FileOpenMulti",
732   "WordWrap",
733   NULL
734 };
735 
736 static const char* tbar_keys[] = {
737   "WrapToolbar",
738   "ToolbarButtonSize",
739   NULL
740 };
741 
742 
743 static const char* global_sect="GlobalStyle";
744 
745 static const char* main_sect = "Settings";
746 static const char* geom_sect = "Geometry";
747 static const char* edit_sect = "Editor";
748 static const char* view_sect = "ViewMenu";
749 static const char* tbar_sect = "Toolbar";
750 static const char* keys_sect = "Keybindings";
751 static const char* popup_sect = "PopupMenu";
752 
753 
754 static const char*  sectnames[] = {  main_sect,  geom_sect,  edit_sect,  view_sect,  tbar_sect,  NULL};
755 static const char** keynames[] =  {  main_keys,  geom_keys,  edit_keys,  view_keys,  tbar_keys,  NULL};
756 
757 
758 
GetSectForKey(const char * key)759 static const char *GetSectForKey(const char*key)
760 {
761   for (FXint i=0; keynames[i]; i++) {
762     for (FXint j=0; keynames[i][j]; j++) {
763       if (strcmp(keynames[i][j],key)==0) { return sectnames[i]; }
764     }
765   }
766   fxerror("FATAL: Section for key \"%s\" not found.\n", key);
767   FXASSERT(!key);
768   return NULL;
769 }
770 
771 
772 
ReadRegInt(FXRegistry * reg,const char * key,FXint def,FXint min=0,FXint max=0)773 static FXint ReadRegInt(FXRegistry *reg, const char*key, FXint def, FXint min=0, FXint max=0)
774 {
775   FXint rv=reg->readIntEntry(GetSectForKey(key),key,def);
776   if (min||max) {
777     if (rv<min) { rv=min; } else if (rv>max) { rv=max; }
778   }
779   return rv;
780 }
781 
782 
783 #define ReadIntRng(k,df,mn,mx) { k=ReadRegInt(reg,""#k,df,mn,mx); }
784 #define ReadInt(k,df) { k=ReadRegInt(reg,""#k,df); }
785 #define WriteInt(k) {reg->writeIntEntry(GetSectForKey(""#k),""#k,k); }
786 
787 
788 #define ReadStr(k,df) { k=reg->readStringEntry(GetSectForKey(""#k),""#k,df); }
789 #define WriteStr(k) {reg->writeStringEntry(GetSectForKey(""#k),""#k,k.text()); }
790 
791 #ifdef FOX_1_6
LocaleIsUTF8()792   FXbool LocaleIsUTF8(){
793 # ifdef WIN32
794     return GetACP()==CP_UTF8;
795 # else
796     const FXchar* str;
797     if((str=getenv("LC_CTYPE"))!=NULL || (str=getenv("LC_ALL"))!=NULL || (str=getenv("LANG"))!=NULL){
798       return (strstr(str,"utf")!=NULL || strstr(str,"UTF")!=NULL);
799     }
800     return false;
801 # endif
802   }
803 #endif
804 
805 
Settings(FXMainWindow * w,const FXString & configdir)806 Settings::Settings(FXMainWindow*w, const FXString &configdir):SettingsBase()
807 {
808   FXString tmp;
809   reg=&(w->getApp()->reg());
810   style_reg=new FXSettings();
811   app=w->getApp();
812   style_file=configdir+STYLE_FILE;
813   if (use_xdg_config()) { style_file.append(".rc"); }
814 
815 #ifdef FOX_1_6
816   // Fox-1.6 will choke reading string entries > 2000 chars, so rewrite
817   // the styles file, in case it was written by a newer version of Fox.
818   if (FXStat::exists(style_file)) {
819     FXFile style_fh(style_file,FXIO::Reading);
820     if (style_fh.isOpen()) {
821       FXString style_data('\0', style_fh.size()+1);
822       style_fh.readBlock((char*)style_data.text(), style_fh.size());
823       style_fh.close();
824       FXint n=style_data.contains(ENDLINE);
825       bool toolong=false;
826       for (FXint i=0; i<=n; i++) {
827         if (style_data.section(ENDLINE,i).length()>1952) {
828           toolong=true;
829           break;
830         }
831       }
832       if (toolong && style_fh.open(style_file,FXIO::Writing)) {
833         FXint eoln=strlen(ENDLINE);
834         for (FXint i=0; i<=n; i++) {
835           FXString line=style_data.section(ENDLINE,i);
836           line.trunc(1952);
837           if (strlen(line.text())) { style_fh.writeBlock(line.text(),line.length()); }
838           style_fh.writeBlock(ENDLINE,eoln);
839         }
840         style_fh.close();
841       }
842     }
843   }
844 #endif
845 
846   if ( (FXStat::exists(style_file)) && (!style_reg->parseFile(style_file, true))) {
847     FXMessageBox::error(app,MBOX_OK,
848       _("Configuration error"), "%s \n%s\n%s", _("Failed to read settings from"),
849       style_file.text(), SystemErrorStr()
850     );
851   }
852   ReadInt(ShowStatusBar,true);
853   ReadInt(ShowLineNumbers,false);
854   ReadInt(ShowToolbar,true);
855   ReadInt(ShowWhiteSpace,false);
856   ReadInt(ShowOutputPane,false);
857   ReadInt(InvertColors,false);
858   ReadIntRng(SplitView,0,SPLIT_NONE,SPLIT_BESIDE);
859   ReadInt(OutputPaneHeight,64);
860   ReadInt(SmartHome,false);
861   ReadInt(WrapAwareHomeEnd,false);
862   ReadIntRng(BraceMatch,BRACEMATCH_EITHER,BRACEMATCH_NONE,BRACEMATCH_AFTER);
863   ReadInt(UseTabs,true);
864   ReadInt(CaretPastEOL,false);
865   ReadIntRng(TabWidth,4,1,16);
866   ReadIntRng(IndentWidth,TabWidth,1,16);
867   ReadIntRng(CaretWidth,1,1,3);
868   ReadIntRng(RightEdgeColumn,80,1,1024);
869   ReadInt(ShowRightEdge,false);
870   ReadInt(ShowIndentGuides,false);
871   ReadInt(ShowCaretLine,true);
872   ReadInt(SmoothScroll,true);
873   ReadIntRng(WheelLines,3,1,32);
874   ReadIntRng(SearchWrap,SEARCH_WRAP_ASK,SEARCH_WRAP_NEVER,SEARCH_WRAP_ASK);
875   ReadIntRng(SearchGui,SEARCH_GUI_BELOW,SEARCH_GUI_ABOVE,SEARCH_GUI_FLOAT);
876   ReadIntRng(AutoIndent,AUTO_INDENT_NONE,AUTO_INDENT_NONE,AUTO_INDENT_SMART);
877   ReadInt(SearchVerbose,true);
878   ReadInt(SearchOptions,0);
879   ReadInt(ZoomFactor,0);
880   ReadIntRng(MaxFiles,128,1,999);
881   ReadInt(PromptCloseMultiMenu,true);
882   ReadInt(PromptCloseMultiExit,true);
883   ReadInt(WatchExternChanges,true);
884   ReadInt(Autosave,false);
885   ReadIntRng(AutosaveInterval,60,15,900);
886   ReadInt(SaveBeforeFilterSel,false);
887   ReadInt(SaveBeforeInsCmd,false);
888   ReadInt(SaveBeforeExecCmd,true);
889   ReadInt(WhitespaceShowsEOL,true);
890   ReadIntRng(DefaultFileFormat,DEFAULT_EOL_FORMAT,0,2);
891   ReadInt(DefaultToAscii,!LocaleIsUTF8());
892   ReadInt(DefaultToSbcs,true);
893   ReadIntRng(KeepFileFilter,REMEMBER_NEVER,REMEMBER_NEVER,REMEMBER_ALWAYS);
894   if (KeepFileFilter==REMEMBER_ALWAYS) {
895     ReadInt(FileFilterIndex,0);
896   } else {
897     FileFilterIndex=0;
898   }
899   ReadInt(FileOpenMulti,false);
900   ReadInt(WordWrap,false);
901   ReadInt(WrapToolbar,true);
902   ReadIntRng(ToolbarButtonSize,1,0,2);// 0=small;  1=medium;  2=large
903 
904   placement=reg->existingEntry(geom_sect,"Top")?PLACEMENT_DEFAULT:PLACEMENT_SCREEN;
905   ReadInt(Left,0);
906   ReadInt(Top,0);
907   ReadInt(Width,0);
908   ReadInt(Height,0);
909 
910   ScreenWidth=w->getApp()->getRootWindow()->getDefaultWidth();
911   ScreenHeight=w->getApp()->getRootWindow()->getDefaultHeight();
912   if ((Width==0)&&(Height==0)) { // First run, size based on screen dimensions
913     Width=ScreenWidth*(4.0/5.0);
914     Height=ScreenHeight*(3.0/4.0);
915     LIMIT_RANGE(Width,600,Width);
916     LIMIT_RANGE(Height,400,Height);
917   }
918 
919   LIMIT_RANGE(Left,0,Left);
920   LIMIT_RANGE(Top,0,Top);
921   LIMIT_RANGE(Width,160,Width);
922   LIMIT_RANGE(Height,120,Height);
923   ReadInt(Maximize,false);
924   ReadIntRng(TabTitleMaxWidth,ScreenWidth/6,0,ScreenWidth);
925   LastFocused=reg->readStringEntry("LastFocused",FXPath::title(TopWinPub::Connector()).text(),"");
926   ReadInt(FontSize,120);
927   ReadIntRng(FontAscent,2,0,16);
928   ReadIntRng(FontDescent,0,0,16);
929 
930   if (reg->existingEntry(edit_sect,"FontName")) {
931     ReadStr(FontName,"Fixed [Misc]");
932   } else {
933     FindFont(FontName);
934   }
935 
936 
937   tmp=reg->readStringEntry(view_sect,"DocTabPosition","T");
938   if (tmp.empty() || !strchr("TBLR",tmp.text()[0])) {
939     DocTabPosition='T';
940   } else {
941     DocTabPosition=tmp.text()[0];
942   }
943 
944   tmp=reg->readStringEntry(view_sect,"DocTabsPacked","A");
945   if (tmp=="0") { tmp="U"; } else if (tmp=="1") { tmp="P"; }
946   if (tmp.empty() || !strchr("UPA",tmp.text()[0])) {
947     DocTabsPacked='A';
948   } else {
949     DocTabsPacked=tmp.text()[0];
950   }
951 
952   ReadStr(FileFilters, default_file_filters);
953   FileFilters.substitute("|", "\n", true);
954   ReadStr(ShellCommand,SHELL_COMMAND);
955 
956   ParseStyles(style_reg, global_sect, GlobalStyle);
957   ParseStyle(style_reg, global_sect, &WhiteSpaceStyle);
958   ParseStyle(style_reg, global_sect, &CaretLineStyle);
959   ParseStyle(style_reg, global_sect, &RightMarginStyle);
960   for (LangStyle*ls=languages; ls->name; ls++) {
961     if (ls->words) {
962       int i;
963       for (i=0; ls->words[i]; i++) {
964         char buf[256];
965         snprintf(buf,sizeof(buf)-1, "words_%d", i+1);
966         tmp=style_reg->readStringEntry(ls->name,buf,ls->words[i]);
967         SetKeywordList(ls,i,tmp);
968       }
969     }
970     ParseStyles(style_reg, ls->name, ls->styles);
971     tmp=style_reg->readStringEntry(ls->name,"FileMask",ls->mask?ls->mask:"");
972     if ( (ls->mask && (strcmp(ls->mask,tmp.text())!=0))||((ls->mask==NULL)&&(!tmp.empty())) ) {
973       ls->mask=strdup(tmp.text());
974       ls->mallocs |= LANGSTYLE_MASK_ALLOCD;
975     }
976     tmp=style_reg->readStringEntry(ls->name,"ShebangApps",ls->apps?ls->apps:"");
977     if ( (ls->apps && (strcmp(ls->apps,tmp.text())!=0))||((ls->apps==NULL)&&(!tmp.empty())) ){
978       ls->apps=strdup(tmp.text());
979       ls->mallocs |= LANGSTYLE_APPS_ALLOCD;
980     }
981     ls->tabs=(TabPolicy)(style_reg->readIntEntry(ls->name,"TabPolicy", ls->tabs));
982     ls->tabwidth=style_reg->readIntEntry(ls->name,"TabWidth", 0);
983     LIMIT_RANGE(ls->tabwidth,0,16);
984   }
985   styles=GlobalStyle;
986   MenuMgr::ReadMenuSpecs(reg,keys_sect);
987   MenuMgr::ReadToolbarButtons(reg,tbar_sect);
988   MenuMgr::ReadPopupMenu(reg,popup_sect);
989   ReadErrorPatterns(reg);
990   ReadSysIncPaths(reg);
991 }
992 
993 
994 
995 extern "C" { void ini_sort(const char *filename); }
996 
997 
~Settings()998 Settings::~Settings()
999 {
1000   LangStyle*ls;
1001   for (ls=languages; ls->name; ls++) {
1002     int i;
1003     SaveStyles(style_reg, ls->name, ls->styles);
1004     style_reg->writeStringEntry(ls->name,"FileMask",ls->mask?ls->mask:"");
1005     style_reg->writeStringEntry(ls->name,"ShebangApps",ls->apps?ls->apps:"");
1006     style_reg->writeIntEntry(ls->name,"TabPolicy", ls->tabs);
1007     style_reg->writeIntEntry(ls->name,"TabWidth", ls->tabwidth);
1008     for (i=0; ls->words[i]; i++) {
1009       char key[32];
1010       snprintf(key, sizeof(key)-1, "words_%d", i+1);
1011 #ifdef FOX_1_6
1012     // fox 1.6 will choke reading string entries > 2000 chars
1013     char buf[1952];
1014     memset(buf,0,sizeof(buf));
1015     strncpy(buf, ls->words[i], sizeof(buf)-1);
1016     style_reg->writeStringEntry(ls->name,key,buf);
1017 #else
1018     style_reg->writeStringEntry(ls->name,key,ls->words[i]);
1019 #endif
1020     }
1021   }
1022   SaveStyles(style_reg, global_sect, GlobalStyle);
1023   SaveStyle(style_reg, global_sect,&WhiteSpaceStyle);
1024   SaveStyle(style_reg, global_sect,&CaretLineStyle);
1025   SaveStyle(style_reg, global_sect,&RightMarginStyle);
1026 
1027   reg->deleteSection(keys_sect);
1028   reg->deleteSection(tbar_sect);
1029 
1030   WriteInt(ShowStatusBar);
1031   WriteInt(ShowWhiteSpace);
1032   WriteInt(ShowOutputPane);
1033   WriteInt(InvertColors);
1034   WriteInt(SplitView);
1035   WriteInt(OutputPaneHeight);
1036   WriteInt(ShowLineNumbers);
1037   WriteInt(ShowToolbar);
1038   WriteInt(SmartHome);
1039   WriteInt(WrapAwareHomeEnd);
1040   WriteInt(AutoIndent);
1041   WriteInt(BraceMatch);
1042   WriteInt(UseTabs);
1043   WriteInt(CaretPastEOL);
1044   WriteInt(TabWidth);
1045   WriteInt(IndentWidth);
1046   WriteInt(CaretWidth);
1047   WriteInt(RightEdgeColumn);
1048   WriteInt(ShowRightEdge);
1049   WriteInt(ShowIndentGuides);
1050   WriteInt(ShowCaretLine);
1051   WriteInt(SmoothScroll);
1052   WriteInt(WheelLines);
1053   WriteInt(SearchWrap);
1054   WriteInt(SearchGui);
1055   WriteInt(SearchVerbose);
1056   WriteInt(SearchOptions);
1057   WriteInt(ZoomFactor);
1058   WriteInt(MaxFiles);
1059   WriteInt(PromptCloseMultiMenu);
1060   WriteInt(PromptCloseMultiExit);
1061   WriteInt(WatchExternChanges);
1062   WriteInt(Autosave);
1063   WriteInt(AutosaveInterval);
1064   WriteInt(SaveBeforeFilterSel);
1065   WriteInt(SaveBeforeInsCmd);
1066   WriteInt(SaveBeforeExecCmd);
1067   WriteInt(WhitespaceShowsEOL);
1068   WriteInt(DefaultFileFormat);
1069   WriteInt(WrapToolbar);
1070   WriteInt(WordWrap);
1071   WriteInt(ToolbarButtonSize);
1072   WriteInt(Left);
1073   WriteInt(Top);
1074   WriteInt(Width);
1075   WriteInt(Height);
1076   WriteInt(Maximize);
1077   reg->writeStringEntry("LastFocused",FXPath::title(TopWinPub::Connector()).text(),LastFocused.text());
1078   WriteInt(FontSize);
1079   WriteInt(FontAscent);
1080   WriteInt(FontDescent);
1081   WriteInt(DefaultToAscii);
1082   WriteInt(DefaultToSbcs);
1083   WriteInt(KeepFileFilter);
1084   WriteInt(FileFilterIndex);
1085   WriteInt(FileOpenMulti);
1086   WriteInt(TabTitleMaxWidth);
1087 
1088   if (!(DocTabPosition && strchr("TBLR",DocTabPosition))) { DocTabPosition='T'; }
1089   char dtp[2]={DocTabPosition,'\0'};
1090   reg->writeStringEntry(view_sect,"DocTabPosition",dtp);
1091 
1092   if (!(DocTabsPacked && strchr("UPA",DocTabsPacked))) { DocTabsPacked='T'; }
1093   dtp[0]=DocTabsPacked;
1094   reg->writeStringEntry(view_sect,"DocTabsPacked",dtp);
1095 
1096   WriteStr(FontName);
1097   WriteStr(FileFilters);
1098   WriteStr(ShellCommand);
1099 
1100   FreeAllKeywordLists();
1101 
1102   MenuMgr::WriteMenuSpecs(reg,keys_sect);
1103   MenuMgr::WriteToolbarButtons(reg,tbar_sect);
1104   MenuMgr::WritePopupMenu(reg,popup_sect);
1105   SaveErrorPatterns(reg);
1106   SaveSysIncPaths(reg);
1107 
1108   if (!style_reg->unparseFile(style_file)) {
1109     FXMessageBox::error(app,MBOX_OK,
1110       _("Configuration error"), "%s \n%s\n%s", _("Failed to save settings to"),
1111       style_file.text(), SystemErrorStr()
1112     );
1113   }
1114   delete style_reg;
1115   ini_sort(style_file.text());
1116 }
1117 
1118