Package Gnumed :: Package timelinelib :: Package config :: Module dotfile
[frames] | no frames]

Source Code for Module Gnumed.timelinelib.config.dotfile

  1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg 
  2  # 
  3  # This file is part of Timeline. 
  4  # 
  5  # Timeline 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 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Timeline 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 Timeline.  If not, see <http://www.gnu.org/licenses/>. 
 17   
 18   
 19  """ 
 20  Handle application configuration. 
 21   
 22  This module is global and can be used by all modules. Before accessing 
 23  configurations, the read function should be called. To save the current 
 24  configuration back to file, call the write method. 
 25  """ 
 26   
 27   
 28  from ConfigParser import ConfigParser 
 29  from ConfigParser import DEFAULTSECT 
 30  import os.path 
 31  import sys 
 32   
 33  import wx 
 34   
 35  from timelinelib.calendar.gregorian.dateformatter import GregorianDateFormatter 
 36  from timelinelib.config.dateformatparser import DateFormatParser 
 37  from timelinelib.general.observer import Observable 
 38  from timelinelib.wxgui.components.font import Font 
 39  from timelinelib.wxgui.utils import display_information_message 
 40   
 41   
 42  # Name used in ConfigParser 
 43  SELECTED_EVENT_BOX_DRAWER = "selected_event_box_drawer" 
 44  WINDOW_WIDTH = "window_width" 
 45  WINDOW_HEIGHT = "window_height" 
 46  WINDOW_XPOS = "window xpos" 
 47  WINDOW_YPOS = "window ypos" 
 48  RECENT_FILES = "recent_files" 
 49  WEEK_START = "week_start" 
 50  DATE_FORMAT = "date_format" 
 51  DEFAULTS = { 
 52      SELECTED_EVENT_BOX_DRAWER: "Default Event box drawer", 
 53      WINDOW_WIDTH: "900", 
 54      WINDOW_HEIGHT: "500", 
 55      WINDOW_XPOS: "-1", 
 56      WINDOW_YPOS: "-1", 
 57      RECENT_FILES: "", 
 58      WEEK_START: "monday", 
 59      DATE_FORMAT: "yyyy-mm-dd", 
 60  } 
 61  # Some settings 
 62  MAX_NBR_OF_RECENT_FILES_SAVED = 5 
 63  ENCODING = "utf-8" 
 64   
 65   
66 -def read_config(path):
67 config = Config(path) 68 config.read() 69 return config
70 71
72 -class Config(Observable):
73 74 """ 75 Provide read and write access to application configuration settings. 76 77 Built as a wrapper around ConfigParser: Properties exist to read and write 78 values but ConfigParser does the actual reading and writing of the 79 configuration file. 80 """ 81
82 - def __init__(self, path):
83 Observable.__init__(self) 84 self.path = path 85 self.config_parser = ConfigParser(DEFAULTS)
86
87 - def read(self):
88 """Read settings from file specified in constructor.""" 89 if self.path: 90 self.config_parser.read(self.path)
91
92 - def write(self):
93 """ 94 Write settings to file specified in constructor and raise IOError if 95 failed. 96 """ 97 f = open(self.path, "w") 98 try: 99 self.config_parser.write(f) 100 finally: 101 f.close()
102
104 return self.config_parser.get(DEFAULTSECT, SELECTED_EVENT_BOX_DRAWER).decode("utf-8")
105
106 - def set_selected_event_box_drawer(self, selected):
107 self.config_parser.set(DEFAULTSECT, SELECTED_EVENT_BOX_DRAWER, str(selected.encode("utf-8")))
108
109 - def get_window_size(self):
110 return (self.config_parser.getint(DEFAULTSECT, WINDOW_WIDTH), 111 self.config_parser.getint(DEFAULTSECT, WINDOW_HEIGHT))
112
113 - def set_window_size(self, size):
114 width, height = size 115 self.config_parser.set(DEFAULTSECT, WINDOW_WIDTH, str(width)) 116 self.config_parser.set(DEFAULTSECT, WINDOW_HEIGHT, str(height))
117
118 - def get_window_pos(self):
119 width, _ = self.get_window_size() 120 # Make sure that some area of the window is visible on the screen 121 # Some part of the titlebar must be visible 122 xpos = max(-width + 100, 123 self.config_parser.getint(DEFAULTSECT, WINDOW_XPOS)) 124 # Titlebar must not be above the upper screen border 125 ypos = max(0, self.config_parser.getint(DEFAULTSECT, WINDOW_YPOS)) 126 return (xpos, ypos)
127
128 - def set_window_pos(self, pos):
129 xpos, ypos = pos 130 self.config_parser.set(DEFAULTSECT, WINDOW_XPOS, str(xpos)) 131 self.config_parser.set(DEFAULTSECT, WINDOW_YPOS, str(ypos))
132
133 - def get_recently_opened(self):
134 ro = self.config_parser.get(DEFAULTSECT, RECENT_FILES).decode(ENCODING).split(",") 135 # Filter out empty elements: "".split(",") will return [""] but we want 136 # the empty list 137 ro_filtered = [x for x in ro if x] 138 return ro_filtered
139
141 if not self.open_recent_at_startup: 142 return False 143 else: 144 return len(self.get_recently_opened()) > 0
145
147 return self.get_recently_opened()[0]
148
149 - def append_recently_opened(self, path):
150 if path in [":tutorial:"]: 151 # Special timelines should not be saved 152 return 153 if isinstance(path, str): 154 # This path might have come from the command line so we need to convert 155 # it to unicode 156 path = path.decode(sys.getfilesystemencoding()) 157 abs_path = os.path.abspath(path) 158 current = self.get_recently_opened() 159 # Just keep one entry of the same path in the list 160 if abs_path in current: 161 current.remove(abs_path) 162 current.insert(0, abs_path) 163 self.config_parser.set(DEFAULTSECT, RECENT_FILES, 164 (",".join(current[:MAX_NBR_OF_RECENT_FILES_SAVED])).encode(ENCODING))
165
166 - def get_week_start(self):
167 return self.config_parser.get(DEFAULTSECT, WEEK_START)
168
169 - def set_week_start(self, week_start):
170 if week_start not in ["monday", "sunday"]: 171 raise ValueError("Invalid week start.") 172 self.config_parser.set(DEFAULTSECT, WEEK_START, week_start) 173 self._notify()
174
175 - def get_shortcut_key(self, cfgid, default):
176 try: 177 return self.config_parser.get(DEFAULTSECT, cfgid) 178 except: 179 self.set_shortcut_key(cfgid, default) 180 return default
181
182 - def set_shortcut_key(self, cfgid, value):
183 self.config_parser.set(DEFAULTSECT, cfgid, value)
184
185 - def _string_to_tuple(self, tuple_string):
186 return tuple([int(x.strip()) for x in tuple_string[1:-1].split(",")])
187
188 - def _tuple_to_string(self, tuple_data):
189 return str(tuple_data)
190
191 - def get_date_formatter(self):
192 parser = DateFormatParser().parse(self.get_date_format()) 193 date_formatter = GregorianDateFormatter() 194 date_formatter.set_separators(*parser.get_separators()) 195 date_formatter.set_region_order(*parser.get_region_order()) 196 date_formatter.use_abbreviated_name_for_month(parser.use_abbreviated_month_names()) 197 return date_formatter
198
199 - def get_date_format(self):
200 return self.config_parser.get(DEFAULTSECT, DATE_FORMAT)
201
202 - def set_date_format(self, date_format):
203 self.config_parser.set(DEFAULTSECT, DATE_FORMAT, date_format) 204 self._notify()
205 date_format = property(get_date_format, set_date_format) 206
207 - def _toStr(self, value):
208 try: 209 return str(value) 210 except UnicodeEncodeError: 211 display_information_message(_("Warning"), _("The selected value contains invalid characters and can't be saved"))
212
213 - def _get(self, key):
214 if key in BOOLEANS: 215 return self._get_boolean(key) 216 elif key in INTS: 217 return self._get_int(key) 218 elif key in COLOURS: 219 return self._get_colour(key) 220 elif key in FONTS: 221 return self._get_font(key) 222 else: 223 return self.config_parser.get(DEFAULTSECT, key)
224
225 - def _get_int(self, key):
226 value = self.config_parser.get(DEFAULTSECT, key) 227 return int(value)
228
229 - def _get_boolean(self, key):
230 return self.config_parser.getboolean(DEFAULTSECT, key)
231
232 - def _get_colour(self, key):
233 return self._string_to_tuple(self.config_parser.get(DEFAULTSECT, key))
234
235 - def _get_font(self, key):
236 return self.config_parser.get(DEFAULTSECT, key)
237
238 - def _set(self, key, value):
239 if key in COLOURS: 240 self._set_colour(key, value) 241 elif key in FONTS: 242 self._set_font(key, value) 243 else: 244 if self._toStr(value) is not None: 245 self.config_parser.set(DEFAULTSECT, key, self._toStr(value)) 246 self._notify()
247
248 - def _set_colour(self, key, value):
249 self.config_parser.set(DEFAULTSECT, key, self._tuple_to_string(value))
250
251 - def _set_font(self, key, value):
252 if self._toStr(value) is not None: 253 self.config_parser.set(DEFAULTSECT, key, value) 254 self._notify()
255 256 257 # To add a new boolean, integer, colour or string configuration item 258 # you only have to add that item to one of the dictionaries below. 259 BOOLEAN_CONFIGS = ( 260 {'name': 'show_toolbar', 'default': 'True'}, 261 {'name': 'show_sidebar', 'default': 'True'}, 262 {'name': 'show_legend', 'default': 'True'}, 263 {'name': 'window_maximized', 'default': 'False'}, 264 {'name': 'open_recent_at_startup', 'default': 'True'}, 265 {'name': 'balloon_on_hover', 'default': 'True'}, 266 {'name': 'use_inertial_scrolling', 'default': 'False'}, 267 {'name': 'never_show_period_events_as_point_events', 'default': 'False'}, 268 {'name': 'draw_point_events_to_right', 'default': 'False'}, 269 {'name': 'event_editor_show_period', 'default': 'False'}, 270 {'name': 'event_editor_show_time', 'default': 'False'}, 271 {'name': 'center_event_texts', 'default': 'False'}, 272 {'name': 'uncheck_time_for_new_events', 'default': 'False'}, 273 {'name': 'text_below_icon', 'default': 'False'}, 274 {'name': 'filtered_listbox_export', 'default': 'False'}, 275 {'name': 'colorize_weekends', 'default': 'False'}, 276 {'name': 'skip_s_in_decade_text', 'default': 'False'}, 277 {'name': 'display_checkmark_on_events_done', 'default': 'False'}, 278 {'name': 'never_use_time', 'default': 'False'}, 279 {'name': 'hide_events_done', 'default': 'False'}, 280 ) 281 INT_CONFIGS = ( 282 {'name': 'sidebar_width', 'default': '200'}, 283 {'name': 'divider_line_slider_pos', 'default': '50'}, 284 {'name': 'vertical_space_between_events', 'default': '5'}, 285 {'name': 'legend_pos', 'default': '0'}, 286 ) 287 STR_CONFIGS = ( 288 {'name': 'experimental_features', 'default': ''}, 289 {'name': 'event_editor_tab_order', 'default': '01234:'}, 290 {'name': 'fuzzy_icon', 'default': 'fuzzy.png'}, 291 {'name': 'locked_icon', 'default': 'locked.png'}, 292 {'name': 'hyperlink_icon', 'default': 'hyperlink.png'}, 293 ) 294 COLOUR_CONFIGS = ( 295 {'name': 'now_line_colour', 'default': '(200, 0, 0)'}, 296 {'name': 'weekend_colour', 'default': '(255, 255, 255)'}, 297 {'name': 'bg_colour', 'default': '(255, 255, 255)'}, 298 {'name': 'minor_strip_divider_line_colour', 'default': '(200, 200, 200)'}, 299 {'name': 'major_strip_divider_line_colour', 'default': '(200, 200, 200)'}, 300 ) 301 FONT_CONFIGS = ( 302 {'name': 'minor_strip_font', 'default': '10:74:90:90:False:Tahoma:33:(0, 0, 0, 255)'}, 303 {'name': 'major_strip_font', 'default': '10:74:90:90:False:Tahoma:33:(0, 0, 0, 255)'}, 304 {'name': 'legend_font', 'default': '10:74:90:90:False:Tahoma:33:(0, 0, 0, 255)'}, 305 {'name': 'balloon_font', 'default': '10:74:90:90:False:Tahoma:33:(0, 0, 0, 255)'}, 306 ) 307 BOOLEANS = [d['name'] for d in BOOLEAN_CONFIGS] 308 INTS = [d['name'] for d in INT_CONFIGS] 309 COLOURS = [d['name'] for d in COLOUR_CONFIGS] 310 FONTS = [d['name'] for d in FONT_CONFIGS] 311 312
313 -def setatt(name):
314 setattr(Config, name, property(lambda self: self._get(name), 315 lambda self, value: self._set(name, str(value))))
316 317 # Create properties dynamically 318 for data in BOOLEAN_CONFIGS + INT_CONFIGS + STR_CONFIGS + COLOUR_CONFIGS + FONT_CONFIGS: 319 setatt(data['name']) 320 DEFAULTS[data['name']] = data['default'] 321