Gnumed.timelinelib.config.dotfile

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