示例#1
0
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)
示例#2
0
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)