class Application (object): """Main application singleton. This class serves as a global container for everything that needs to be shared in the GUI. Its constructor is the last part of the initialization, called by main.py or by the testing scripts. Access via `gui.application.get_app()`. """ #: Singleton instance _INSTANCE = None def __init__(self, filenames, app_datapath, app_extradatapath, user_datapath, user_confpath, version, fullscreen=False): """Construct, but do not run. :params filenames: The list of files to load. Note: only the first is used. :param app_datapath: App-specific read-only data area. Path used for UI definition XML, and the default sets of backgrounds, palettes, and brush defintions. Often $PREFIX/share/. :param app_extradatapath: Extra search path for themeable UI icons. This will be used in addition to $XDG_DATA_DIRS for the purposes of icon lookup. Normally it's $PREFIX/share, to support unusual installations outside the usual locations. It should contain an icons/ subdirectory. :param user_datapath: Location of the user's app-specific data. For MyPaint, this means the user's brushes, backgrounds, and scratchpads. Commonly $XDG_DATA_HOME/mypaint, i.e. ~/.local/share/mypaint :param user_confpath: Location of the user's app-specific config area. This is where MyPaint will save user preferences data and the keyboard accelerator map. Commonly $XDG_CONFIG_HOME/mypaint, i.e. ~/.config/mypaint :param version: Version string for the about dialog. :param fullscreen: Go fullscreen after starting. """ assert Application._INSTANCE is None super(Application, self).__init__() Application._INSTANCE = self self.user_confpath = user_confpath #: User configs (see __init__) self.user_datapath = user_datapath #: User data (see __init__) self.datapath = app_datapath self.version = version #: version string for the app. # create config directory, and subdirs where the user might drop files for basedir in [self.user_confpath, self.user_datapath]: if not os.path.isdir(basedir): os.mkdir(basedir) print 'Created basedir', basedir for datasubdir in ['backgrounds', 'brushes', 'scratchpads']: datadir = os.path.join(self.user_datapath, datasubdir) if not os.path.isdir(datadir): os.mkdir(datadir) print 'Created data subdir', datadir # Default location for our icons. The user's theme can override these. icon_theme = gtk.icon_theme_get_default() icon_theme.append_search_path(join(app_extradatapath, "icons")) # Icon sanity check if not icon_theme.has_icon('mypaint') \ or not icon_theme.has_icon('mypaint-tool-brush'): print 'Error: Where have my icons gone?' print 'Icon search path:', icon_theme.get_search_path() print "Mypaint can't run sensibly without its icons; " \ + "please check your installation." print 'see https://gna.org/bugs/?18460 for possible solutions' sys.exit(1) if pygtkcompat.USE_GTK3: gtk.Window.set_default_icon_name('mypaint') else: gtk.window_set_default_icon_name('mypaint') # Stock items, core actions, and menu structure builder_xml = join(self.datapath, "gui", "mypaint.xml") self.builder = gtk.Builder() self.builder.set_translation_domain("mypaint") self.builder.add_from_file(builder_xml) factory = self.builder.get_object("stock_icon_factory") factory.add_default() self.ui_manager = self.builder.get_object("app_ui_manager") signal_callback_objs = [] gdk.set_program_class('MyPaint') self.pixmaps = PixbufDirectory(join(self.datapath, 'pixmaps')) self.cursor_color_picker = gdk.Cursor( pygtkcompat.gdk.display_get_default(), self.pixmaps.cursor_color_picker, 1, 30) self.cursors = CursorCache(self) # unmanaged main brush; always the same instance (we can attach settings_observers) # this brush is where temporary changes (color, size...) happen self.brush = brush.BrushInfo() self.brush.load_defaults() # Global pressure mapping function, ignored unless set self.pressure_mapping = None self.preferences = {} self.load_settings() self.scratchpad_filename = "" self.kbm = keyboard.KeyboardManager(self) self.doc = document.Document(self) signal_callback_objs.append(self.doc) signal_callback_objs.append(self.doc.modes) self.scratchpad_doc = document.Document(self, leader=self.doc) self.brushmanager = brushmanager.BrushManager( join(app_datapath, 'brushes'), join(user_datapath, 'brushes'), self) self.filehandler = filehandling.FileHandler(self) signal_callback_objs.append(self.filehandler) self.brushmodifier = brushmodifier.BrushModifier(self) self.line_mode_settings = linemode.LineModeSettings(self) # Button press mapping self.button_mapping = ButtonMapping() # Monitors changes of input device & saves device-specific brushes self.device_monitor = DeviceUseMonitor(self) if not self.preferences.get("scratchpad.last_opened_scratchpad", None): self.preferences["scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave() self.scratchpad_filename = self.preferences["scratchpad.last_opened_scratchpad"] self.brush_color_manager = BrushColorManager(self) self.brush_color_manager.set_picker_cursor(self.cursor_color_picker) self.brush_color_manager.set_data_path(self.datapath) self.init_brush_adjustments() self.layout_manager = layout.LayoutManager( prefs=self.preferences["layout.window_positions"], factory=windowing.window_factory, factory_opts=[self] ) self.drawWindow = self.layout_manager.get_widget_by_role("main-window") self.layout_manager.show_all() signal_callback_objs.append(self.drawWindow) # Connect signals defined in mypaint.xml callback_finder = CallbackFinder(signal_callback_objs) self.builder.connect_signals(callback_finder) self.kbm.start_listening() self.filehandler.doc = self.doc self.filehandler.filename = None pygtkcompat.gtk.accel_map_load(join(self.user_confpath, 'accelmap.conf')) # Load the background settings window. # FIXME: this line shouldn't be needed, but we need to load this up # front to get any non-default background that the user has configured # from the preferences. self.layout_manager.get_subwindow_by_role("backgroundWindow") # And the brush settings window, or things like eraser mode will break. # FIXME: brush_adjustments should not be dependent on this self.layout_manager.get_subwindow_by_role("brushSettingsWindow") def at_application_start(*junk): col = self.brush_color_manager.get_color() self.brushmanager.select_initial_brush() self.brush_color_manager.set_color(col) if filenames: # Open only the first file, no matter how many has been specified # If the file does not exist just set it as the file to save to fn = filenames[0].replace('file:///', '/') # ^ some filebrowsers do this (should only happen with outdated # mypaint.desktop) if not os.path.exists(fn): self.filehandler.filename = fn else: self.filehandler.open_file(fn) # Load last scratchpad if not self.preferences["scratchpad.last_opened_scratchpad"]: self.preferences["scratchpad.last_opened_scratchpad"] \ = self.filehandler.get_scratchpad_autosave() self.scratchpad_filename \ = self.preferences["scratchpad.last_opened_scratchpad"] if os.path.isfile(self.scratchpad_filename): try: self.filehandler.open_scratchpad(self.scratchpad_filename) except AttributeError, e: print "Scratchpad widget isn't initialised yet, so cannot centre" self.apply_settings() if not self.pressure_devices: print 'No pressure sensitive devices found.' self.drawWindow.present() # Handle fullscreen command line option if fullscreen: self.drawWindow.fullscreen_cb() gobject.idle_add(at_application_start)
class Application: # singleton """ This class serves as a global container for everything that needs to be shared in the GUI. Its constructor is the last part of the initialization, called by main.py or by the testing scripts. """ def __init__(self, datapath, extradata, confpath, filenames): """Construct, but do not run. :`datapath`: Usually ``$PREFIX/share/mypaint``. Where MyPaint should find its app-specific read-only data, e.g. UI definition XML, backgrounds and brush defintions. :`extradata`: Where to find the defaults for MyPaint's themeable UI icons. This will be effectively used in addition to ``$XDG_DATA_DIRS`` for the purposes of icon lookup. Normally it's ``$PREFIX/share``, to support unusual installations outside the usual locations. It should contain an ``icons/`` subdirectory. :`confpath`: Where the user's configuration is stored. ``$HOME/.mypaint`` is typical on Unix-like OSes. """ self.confpath = confpath self.datapath = datapath # create config directory, and subdirs where the user might drop files # TODO make scratchpad dir something pulled from preferences #PALETTE1 for d in ['', 'backgrounds', 'brushes', 'scratchpads']: d = os.path.join(self.confpath, d) if not os.path.isdir(d): os.mkdir(d) print 'Created', d # Default location for our icons. The user's theme can override these. icon_theme = gtk.icon_theme_get_default() icon_theme.append_search_path(join(extradata, "icons")) # Icon sanity check if not icon_theme.has_icon('mypaint') \ or not icon_theme.has_icon('mypaint-tool-brush'): print 'Error: Where have my icons gone?' print 'Icon search path:', icon_theme.get_search_path() print "Mypaint can't run sensibly without its icons; " \ + "please check your installation." print 'see https://gna.org/bugs/?18460 for possible solutions' sys.exit(1) if pygtkcompat.USE_GTK3: gtk.Window.set_default_icon_name('mypaint') else: gtk.window_set_default_icon_name('mypaint') # Stock items, core actions, and menu structure builder_xml = join(datapath, "gui", "mypaint.xml") self.builder = gtk.Builder() self.builder.set_translation_domain("mypaint") self.builder.add_from_file(builder_xml) factory = self.builder.get_object("stock_icon_factory") factory.add_default() self.ui_manager = self.builder.get_object("app_ui_manager") signal_callback_objs = [] gdk.set_program_class('MyPaint') self.pixmaps = PixbufDirectory(join(self.datapath, 'pixmaps')) self.cursor_color_picker = gdk.Cursor( pygtkcompat.gdk.display_get_default(), self.pixmaps.cursor_color_picker, 1, 30) self.cursors = CursorCache(self) # unmanaged main brush; always the same instance (we can attach settings_observers) # this brush is where temporary changes (color, size...) happen self.brush = brush.BrushInfo() self.brush.load_defaults() # Global pressure mapping function, ignored unless set self.pressure_mapping = None self.preferences = {} self.load_settings() self.scratchpad_filename = "" self.kbm = keyboard.KeyboardManager(self) self.doc = document.Document(self) signal_callback_objs.append(self.doc) signal_callback_objs.append(self.doc.modes) self.scratchpad_doc = document.Document(self, leader=self.doc) self.brushmanager = brushmanager.BrushManager(join(datapath, 'brushes'), join(confpath, 'brushes'), self) self.filehandler = filehandling.FileHandler(self) signal_callback_objs.append(self.filehandler) self.brushmodifier = brushmodifier.BrushModifier(self) self.line_mode_settings = linemode.LineModeSettings(self) # Button press mapping self.button_mapping = ButtonMapping() # Monitors changes of input device & saves device-specific brushes self.device_monitor = DeviceUseMonitor(self) if not self.preferences.get("scratchpad.last_opened_scratchpad", None): self.preferences["scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave() self.scratchpad_filename = self.preferences["scratchpad.last_opened_scratchpad"] self.brush_color_manager = BrushColorManager(self) self.brush_color_manager.set_picker_cursor(self.cursor_color_picker) self.brush_color_manager.set_data_path(datapath) self.init_brush_adjustments() self.layout_manager = layout.LayoutManager( prefs=self.preferences["layout.window_positions"], factory=windowing.window_factory, factory_opts=[self] ) self.drawWindow = self.layout_manager.get_widget_by_role("main-window") self.layout_manager.show_all() signal_callback_objs.append(self.drawWindow) # Connect signals defined in mypaint.xml callback_finder = CallbackFinder(signal_callback_objs) self.builder.connect_signals(callback_finder) self.kbm.start_listening() self.filehandler.doc = self.doc self.filehandler.filename = None pygtkcompat.gtk.accel_map_load(join(self.confpath, 'accelmap.conf')) # Load the background settings window. # FIXME: this line shouldn't be needed, but we need to load this up # front to get any non-default background that the user has configured # from the preferences. self.layout_manager.get_subwindow_by_role("backgroundWindow") # And the brush settings window, or things like eraser mode will break. # FIXME: brush_adjustments should not be dependent on this self.layout_manager.get_subwindow_by_role("brushSettingsWindow") def at_application_start(*junk): col = self.brush_color_manager.get_color() self.brushmanager.select_initial_brush() self.brush_color_manager.set_color(col) if filenames: # Open only the first file, no matter how many has been specified # If the file does not exist just set it as the file to save to fn = filenames[0].replace('file:///', '/') # some filebrowsers do this (should only happen with outdated mypaint.desktop) if not os.path.exists(fn): self.filehandler.filename = fn else: self.filehandler.open_file(fn) # Load last scratchpad if not self.preferences["scratchpad.last_opened_scratchpad"]: self.preferences["scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave() self.scratchpad_filename = self.preferences["scratchpad.last_opened_scratchpad"] if os.path.isfile(self.scratchpad_filename): try: self.filehandler.open_scratchpad(self.scratchpad_filename) except AttributeError, e: print "Scratchpad widget isn't initialised yet, so cannot centre" self.apply_settings() if not self.pressure_devices: print 'No pressure sensitive devices found.' self.drawWindow.present() gobject.idle_add(at_application_start)