startup: add support for XDG user config dirs · mypaint/mypaint@cf723b7

@@ -10,8 +10,9 @@

1010

import gettext

1111

import os, sys

1212

from os.path import join

13-

import gtk, gobject

14-

gdk = gtk.gdk

13+

import gobject

14+

import gtk

15+

from gtk import gdk

1516

from lib import brush, helpers, mypaintlib

1617

import filehandling, keyboard, brushmanager, windowing, document, layout

1718

import brushmodifier, linemode

@@ -29,37 +30,51 @@ class Application: # singleton

2930

initialization, called by main.py or by the testing scripts.

3031

"""

313232-

def __init__(self, datapath, extradata, confpath, filenames):

33+

def __init__(self, filenames, app_datapath, app_extradatapath,

34+

user_datapath, user_confpath, fullscreen=False):

3335

"""Construct, but do not run.

343635-

:`datapath`:

36-

Usually ``$PREFIX/share/mypaint``. Where MyPaint should find its

37-

app-specific read-only data, e.g. UI definition XML, backgrounds

38-

and brush defintions.

39-

:`extradata`:

40-

Where to find the defaults for MyPaint's themeable UI icons. This

41-

will be effectively used in addition to ``$XDG_DATA_DIRS`` for the

42-

purposes of icon lookup. Normally it's ``$PREFIX/share``, to support

43-

unusual installations outside the usual locations. It should contain

44-

an ``icons/`` subdirectory.

45-

:`confpath`:

46-

Where the user's configuration is stored. ``$HOME/.mypaint`` is

47-

typical on Unix-like OSes.

37+

:params filenames: The list of files to load.

38+

Note: only the first is used.

39+

:param app_datapath: App-specific read-only data area.

40+

Path used for UI definition XML, and the default sets of backgrounds,

41+

palettes, and brush defintions. Often $PREFIX/share/.

42+

:param app_extradatapath: Extra search path for themeable UI icons.

43+

This will be used in addition to $XDG_DATA_DIRS for the purposes of

44+

icon lookup. Normally it's $PREFIX/share, to support unusual

45+

installations outside the usual locations. It should contain an

46+

icons/ subdirectory.

47+

:param user_datapath: Location of the user's app-specific data.

48+

For MyPaint, this means the user's brushes, backgrounds, and

49+

scratchpads. Commonly $XDG_DATA_HOME/mypaint, i.e.

50+

~/.local/share/mypaint

51+

:param user_confpath: Location of the user's app-specific config area.

52+

This is where MyPaint will save user preferences data and the

53+

keyboard accelerator map. Commonly $XDG_CONFIG_HOME/mypaint, i.e.

54+

~/.config/mypaint

55+

:param fullscreen: Go fullscreen after starting.

56+4857

"""

49-

self.confpath = confpath

50-

self.datapath = datapath

58+59+

self.user_confpath = user_confpath #: User configs (see __init__)

60+

self.user_datapath = user_datapath #: User data (see __init__)

61+62+

self.datapath = app_datapath

51635264

# create config directory, and subdirs where the user might drop files

53-

# TODO make scratchpad dir something pulled from preferences #PALETTE1

54-

for d in ['', 'backgrounds', 'brushes', 'scratchpads']:

55-

d = os.path.join(self.confpath, d)

56-

if not os.path.isdir(d):

57-

os.mkdir(d)

58-

print 'Created', d

65+

for basedir in [self.user_confpath, self.user_datapath]:

66+

if not os.path.isdir(basedir):

67+

os.mkdir(basedir)

68+

print 'Created basedir', basedir

69+

for datasubdir in ['backgrounds', 'brushes', 'scratchpads']:

70+

datadir = os.path.join(self.user_datapath, datasubdir)

71+

if not os.path.isdir(datadir):

72+

os.mkdir(datadir)

73+

print 'Created data subdir', datadir

59746075

# Default location for our icons. The user's theme can override these.

6176

icon_theme = gtk.icon_theme_get_default()

62-

icon_theme.append_search_path(join(extradata, "icons"))

77+

icon_theme.append_search_path(join(app_extradatapath, "icons"))

63786479

# Icon sanity check

6580

if not icon_theme.has_icon('mypaint') \

@@ -77,7 +92,7 @@ def __init__(self, datapath, extradata, confpath, filenames):

7792

gtk.window_set_default_icon_name('mypaint')

78937994

# Stock items, core actions, and menu structure

80-

builder_xml = join(datapath, "gui", "mypaint.xml")

95+

builder_xml = join(self.datapath, "gui", "mypaint.xml")

8196

self.builder = gtk.Builder()

8297

self.builder.set_translation_domain("mypaint")

8398

self.builder.add_from_file(builder_xml)

@@ -113,7 +128,10 @@ def __init__(self, datapath, extradata, confpath, filenames):

113128

signal_callback_objs.append(self.doc)

114129

signal_callback_objs.append(self.doc.modes)

115130

self.scratchpad_doc = document.Document(self, leader=self.doc)

116-

self.brushmanager = brushmanager.BrushManager(join(datapath, 'brushes'), join(confpath, 'brushes'), self)

131+

self.brushmanager = brushmanager.BrushManager(

132+

join(app_datapath, 'brushes'),

133+

join(user_datapath, 'brushes'),

134+

self)

117135

self.filehandler = filehandling.FileHandler(self)

118136

signal_callback_objs.append(self.filehandler)

119137

self.brushmodifier = brushmodifier.BrushModifier(self)

@@ -131,7 +149,7 @@ def __init__(self, datapath, extradata, confpath, filenames):

131149132150

self.brush_color_manager = BrushColorManager(self)

133151

self.brush_color_manager.set_picker_cursor(self.cursor_color_picker)

134-

self.brush_color_manager.set_data_path(datapath)

152+

self.brush_color_manager.set_data_path(self.datapath)

135153136154

self.init_brush_adjustments()

137155

@@ -151,7 +169,8 @@ def __init__(self, datapath, extradata, confpath, filenames):

151169

self.kbm.start_listening()

152170

self.filehandler.doc = self.doc

153171

self.filehandler.filename = None

154-

pygtkcompat.gtk.accel_map_load(join(self.confpath, 'accelmap.conf'))

172+

pygtkcompat.gtk.accel_map_load(join(self.user_confpath,

173+

'accelmap.conf'))

155174156175

# Load the background settings window.

157176

# FIXME: this line shouldn't be needed, but we need to load this up

@@ -170,34 +189,41 @@ def at_application_start(*junk):

170189

if filenames:

171190

# Open only the first file, no matter how many has been specified

172191

# If the file does not exist just set it as the file to save to

173-

fn = filenames[0].replace('file:///', '/') # some filebrowsers do this (should only happen with outdated mypaint.desktop)

192+

fn = filenames[0].replace('file:///', '/')

193+

# ^ some filebrowsers do this (should only happen with outdated

194+

# mypaint.desktop)

174195

if not os.path.exists(fn):

175196

self.filehandler.filename = fn

176197

else:

177198

self.filehandler.open_file(fn)

178199179200

# Load last scratchpad

180201

if not self.preferences["scratchpad.last_opened_scratchpad"]:

181-

self.preferences["scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave()

182-

self.scratchpad_filename = self.preferences["scratchpad.last_opened_scratchpad"]

202+

self.preferences["scratchpad.last_opened_scratchpad"] \

203+

= self.filehandler.get_scratchpad_autosave()

204+

self.scratchpad_filename \

205+

= self.preferences["scratchpad.last_opened_scratchpad"]

183206

if os.path.isfile(self.scratchpad_filename):

184207

try:

185208

self.filehandler.open_scratchpad(self.scratchpad_filename)

186209

except AttributeError, e:

187210

print "Scratchpad widget isn't initialised yet, so cannot centre"

188211189-190212

self.apply_settings()

191213

if not self.pressure_devices:

192214

print 'No pressure sensitive devices found.'

193215

self.drawWindow.present()

194216217+

# Handle fullscreen command line option

218+

if fullscreen:

219+

self.drawWindow.fullscreen_cb()

220+195221

gobject.idle_add(at_application_start)

196222197223

def save_settings(self):

198224

"""Saves the current settings to persistent storage."""

199225

def save_config():

200-

settingspath = join(self.confpath, 'settings.json')

226+

settingspath = join(self.user_confpath, 'settings.json')

201227

jsonstr = helpers.json_dumps(self.preferences)

202228

f = open(settingspath, 'w')

203229

f.write(jsonstr)

@@ -221,15 +247,15 @@ def load_settings(self):

221247

def get_legacy_config():

222248

dummyobj = {}

223249

tmpdict = {}

224-

settingspath = join(self.confpath, 'settings.conf')

250+

settingspath = join(self.user_confpath, 'settings.conf')

225251

if os.path.exists(settingspath):

226252

exec open(settingspath) in dummyobj

227253

tmpdict['saving.scrap_prefix'] = dummyobj['save_scrap_prefix']

228254

tmpdict['input.device_mode'] = dummyobj['input_devices_mode']

229255

tmpdict['input.global_pressure_mapping'] = dummyobj['global_pressure_mapping']

230256

return tmpdict

231257

def get_json_config():

232-

settingspath = join(self.confpath, 'settings.json')

258+

settingspath = join(self.user_confpath, 'settings.json')

233259

jsonstr = open(settingspath).read()

234260

try:

235261

return helpers.json_loads(jsonstr)

@@ -526,7 +552,7 @@ def update_input_devices(self):

526552

print ''

527553528554

def save_gui_config(self):

529-

pygtkcompat.gtk.accel_map_save(join(self.confpath, 'accelmap.conf'))

555+

pygtkcompat.gtk.accel_map_save(join(self.user_confpath, 'accelmap.conf'))

530556

self.save_settings()

531557532558

def message_dialog(self, text, type=gtk.MESSAGE_INFO, flags=0,