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
    103 -    def get_selected_event_box_drawer(self):
    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
    140 -    def has_recently_opened_files(self):
    141          if not self.open_recent_at_startup:
    142              return False
    143          else:
    144              return len(self.get_recently_opened()) > 0
    145
    146 -    def get_latest_recently_opened_file(self):
    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