Skip to content
Permalink
Browse files

ReferenceImageLayer: Add new layer class.

New class `ReferenceImageLayer` added, which refers
a picture file in another location of local filesystem.
Mypaint monitors that file and follows its update.
It is just referred, cannot be modified from mypaint.
ORA file records only its filename and position.

It would be useful when that picture is generated by
another (and processor consuming) program,  such as
blender.

Also, under certain case, LayerPropertiesUI class
shows GTK-warning or causing Segfault.
This bug fixed.

Also, now Vectorlayer can have `blueprint`
as current layer image.
To use this feature, invoke `New Vector layer from current` action
from menu.
  • Loading branch information...
dothiko committed Mar 11, 2019
1 parent 14f94bb commit c49a25c60f7738d6d9088b03477033816cf3a863
Showing with 647 additions and 65 deletions.
  1. +3 −17 gui/document.py
  2. +37 −12 gui/filehandling.py
  3. +23 −1 gui/layerprops.glade
  4. +68 −11 gui/layerprops.py
  5. +2 −0 gui/layerswindow.xml
  6. +3 −0 gui/menu.xml
  7. +26 −0 gui/resources.xml
  8. +67 −0 lib/command.py
  9. +62 −1 lib/document.py
  10. +2 −0 lib/layer/core.py
  11. +350 −20 lib/layer/data.py
  12. +1 −1 lib/layer/group.py
  13. +3 −2 lib/surface.py
@@ -1412,6 +1412,9 @@ def new_layer_cb(self, action):
layer_kwds["y"] = y
layer_kwds["w"] = w
layer_kwds["h"] = h
if "Current" in action.get_name():
layer_kwds["source-layer"] = layers.current

elif "Group" in action.get_name():
layer_class = lib.layer.LayerStack

@@ -1424,23 +1427,6 @@ def new_layer_cb(self, action):
path = layers.path_below(path, insert=True)
assert path is not None

if "Import" in action.get_name():
app = self.app
try:
dlg = app.filehandler.get_open_dialog(
file_filters=app.filehandler.file_filters)
preview = Gtk.Image()
dlg.set_preview_widget(preview)
dlg.connect("update-preview",
app.filehandler.update_preview_cb, preview)

if dlg.run() == Gtk.ResponseType.OK:
layer_kwds["import-filename"] = dlg.get_filename().decode('utf-8')
else:
return
finally:
dlg.destroy()

self.model.add_layer(path, layer_class=layer_class, **layer_kwds)

self.layerblink_state.activate(action)
@@ -859,6 +859,16 @@ def import_layers(self, filenames):
return
logger.info('Imported layers from %r', filenames)

# XXX for `reference layer`
def add_reference_layers(self, filenames):
"""Load a file, replacing the current working document."""

if not self._call_doc_load_method(self.doc.model.add_reference_layers,
filenames, True):
return
logger.info('Add reference layers from %r', filenames)
# XXX for `reference layer` end

def _call_doc_load_method(self, method, arg, is_import):
"""Internal: common GUI aspects of loading or importing files.
@@ -1129,11 +1139,21 @@ def open_scratchpad_dialog(self):

def import_layers_cb(self, action):
"""Action callback: import layers from multiple files."""
dialog = Gtk.FileChooserDialog(
# XXX for `reference layer`
if "Import" in action.get_name():
title = C_(
u'Layers→Import Layers: files-chooser dialog: title',
u"Import Layers",
),
)
else:
title = C_(
u'Layers→Add Reference Layer: files-chooser dialog: title',
u"Add Reference Layer",
)


dialog = Gtk.FileChooserDialog(
title = title,
parent = self.app.drawWindow,
action = Gtk.FileChooserAction.OPEN,
buttons = [
@@ -1152,15 +1172,17 @@ def import_layers_cb(self, action):

_add_filters_to_dialog(self.file_filters, dialog)

# Choose the most recent save folder.
self._update_recent_items()
for item in reversed(self._recent_items):
uri = item.get_uri()
fn, _h = lib.glib.filename_from_uri(uri)
dn = os.path.dirname(fn)
if os.path.isdir(dn):
dialog.set_current_folder(dn)
break
# XXX for `reference layer`
if "Import" in action.get_name():
# Choose the most recent save folder.
self._update_recent_items()
for item in reversed(self._recent_items):
uri = item.get_uri()
fn, _h = lib.glib.filename_from_uri(uri)
dn = os.path.dirname(fn)
if os.path.isdir(dn):
dialog.set_current_folder(dn)
break

filenames = []
try:
@@ -1172,7 +1194,10 @@ def import_layers_cb(self, action):

if filenames:
filenames = [filename_to_unicode(f) for f in filenames]
self.import_layers(filenames)
if "Import" in action.get_name():
self.import_layers(filenames)
else:
self.add_reference_layers(filenames)

def save_cb(self, action):
if not self.filename:
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.22.1 -->
<interface domain="mypaint">
<requires lib="gtk+" version="3.12"/>
<object class="GtkAdjustment" id="layer-opacity-adjustment">
@@ -244,6 +244,28 @@ With marking layer(s),you can do some actions which uses other layer(s) as sourc
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="layer-source-label">
<property name="can_focus">False</property>
<property name="label" translatable="yes">Source</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkFileChooserButton" id="layer-source-chooser">
<property name="can_focus">False</property>
<property name="create_folders">False</property>
<property name="title" translatable="yes"/>
<signal name="file-set" handler="_v_layer_source_changed_cb" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
@@ -24,6 +24,7 @@
import lib.xml
from lib.gettext import C_
import gui.mvp
import lib.layer # XXX for `reference-layer`

import cairo
from gi.repository import Gtk
@@ -115,17 +116,25 @@ def __init__(self, docmodel):
root.layer_thumbnail_updated += self._m_layer_thumbnail_updated_cb
self._store = None

# XXX To detect whether this view is active(i.e. shown as dialog)
# or not.
self.attached = False

def init_view(self):
"""Set initial state of the view objects."""

# 3-column mode liststore (id, name, sensitive)
store = Gtk.ListStore(int, str, bool)
modes = STACK_MODES + STANDARD_MODES
for mode in modes:
label, desc = MODE_STRINGS.get(mode)
store.append([mode, label, True])
self._store = store
self.view.layer_mode_combo.set_model(store)
if self._store is None:
store = Gtk.ListStore(int, str, bool)
modes = STACK_MODES + STANDARD_MODES
for mode in modes:
label, desc = MODE_STRINGS.get(mode)
store.append([mode, label, True])
self._store = store
self.view.layer_mode_combo.set_model(store)
else:
self.view.layer_mode_combo.set_model(self._store)

self.attached = True

# The eye button is greyed out while the view is locked.
lvm = self._docmodel.layer_view_manager
@@ -143,6 +152,15 @@ def widget(self):

@property
def _layer(self):
# XXX Workaround: To avoid GTK-warning and
# Segfault(abort) Mypaint.
# Once Layer-property dialog is destroied,
# the event handler still active, and it causes
# some problems around dialog widgets.
# So add `attached` attribute and use this to
# identify the dialog is exist.
if not self.attached:
return None
root = self._docmodel.layer_stack
return root.current

@@ -192,13 +210,16 @@ def _m_current_view_changed_cb(self, lvm):
self._m2v_layerview_locked()

def _m2v_all(self):
if not self._layer:
return
self._m2v_preview()
self._m2v_name()
self._m2v_mode()
self._m2v_opacity()
for info in self._BOOL_PROPERTIES:
self._m2v_layer_flag(info)
self._m2v_layerview_locked()
self._m2v_refsource() # XXX for `reference_layer`

def _m2v_preview(self):
layer = self._layer
@@ -246,13 +267,15 @@ def _m2v_mode(self):
combo.set_active_iter(active_iter)

def _m2v_opacity(self):
layer = self._layer
if not layer:
return

adj = self.view.layer_opacity_adjustment
scale = self.view.layer_opacity_scale
layer = self._layer

opacity_is_adjustable = not (
layer is None
or layer is self._docmodel.layer_stack
layer is self._docmodel.layer_stack
or layer.mode == PASS_THROUGH_MODE
)
scale.set_sensitive(opacity_is_adjustable)
@@ -264,6 +287,9 @@ def _m2v_opacity(self):

def _m2v_layer_flag(self, info):
layer = self._layer
if not layer:
return

propval = getattr(layer, info.property)
propval_idx = int(propval)

@@ -281,6 +307,22 @@ def _m2v_layerview_locked(self):
btn = self.view.layer_hidden_togglebutton
btn.set_sensitive(sensitive)

# XXX for `reference-layer`
def _m2v_refsource(self):
layer = self._layer
show_flag = isinstance(layer, lib.layer.ReferenceImageLayer)
refsource_label = self.view.layer_source_label
refsource_chooser = self.view.layer_source_chooser
if show_flag:
refsource_label.show()
refsource_chooser.show()
assert hasattr(layer, 'reference_path')
refsource_chooser.set_filename(layer.reference_path)
else:
refsource_label.hide()
refsource_chooser.hide()
# XXX for `reference-layer` end

# View monitoring and response (callback names defined in .glade XML):

def _v_layer_mode_combo_query_tooltip_cb(self, combo, x, y, kbd, tooltip):
@@ -350,6 +392,15 @@ def _v_layer_marked_togglebutton_toggled_cb(self, btn):
if (i.property == "marked")][0]
self._v2m_layer_flag(info)
# XXX 'marked' layer flag end

# XXX for `reference_layer`
@gui.mvp.model_updater
def _v_layer_source_changed_cb(self, btn):
cl = self._layer
if not cl or not isinstance(cl, lib.layer.ReferenceImageLayer):
return
cl.set_reference_path(btn.get_filename())
# XXX for `reference_layer` end

def _v2m_layer_flag(self, info):
layer = self._layer
@@ -420,7 +471,13 @@ def __init__(self, parent, docmodel):
self._ui = LayerPropertiesUI(docmodel)
self.vbox.pack_start(self._ui.widget, True, True, 0)
self.set_default_response(Gtk.ResponseType.OK)
self.connect("destroy", self.destroy_cb) # XXX workaround for GTK segfault

def destroy_cb(self, widget):
"""Destroy handler, to notify LayerPropertiesUI that
the parent dialog is already destroyed.
"""
self._ui.attached = False

# Helpers:

@@ -13,7 +13,9 @@
<placeholder name="BasicListActions">
<menuitem action='NewPaintingLayerAbove'/>
<menuitem action='NewVectorLayerAbove'/>
<menuitem action='NewVectorLayerCurrent'/>
<menuitem action='NewLayerGroupAbove'/>
<menuitem action='NewReferenceLayerAbove'/>
<separator/>
<menuitem action='DuplicateLayer'/>
<menuitem action='RemoveLayer'/>
@@ -248,10 +248,13 @@
<menuitem action='NewPaintingLayerAbove'/>
<menuitem action='NewLayerGroupAbove'/>
<menuitem action='NewVectorLayerAbove'/>
<menuitem action='NewVectorLayerCurrent'/>
<menuitem action='NewReferenceLayerAbove'/>
<menu action="LayerNewBelowMenu">
<menuitem action='NewPaintingLayerBelow'/>
<menuitem action='NewLayerGroupBelow'/>
<menuitem action='NewVectorLayerBelow'/>
<menuitem action='NewReferenceLayerBelow'/>
</menu>
<!-- Current layer: editing: Clipboard -->
<separator/>
@@ -575,6 +575,32 @@ Vocabulary
<signal name="activate" handler="new_layer_cb"/>
</object>
</child>
<!-- XXX for vectorlayer from current -->
<child>
<object class="GtkAction" id="NewVectorLayerCurrent">
<property name="icon-name">mypaint-layer-vector-symbolic</property>
<property name="label" translatable="yes" context="Menu→Layer (labels), Accel Editor (labels)">New Vector Layer from current…</property>
<property name="tooltip" translatable="yes" context="Accel Editor (descriptions)">Add a new vector layer above the current layer which based on currently selected layer, and begin editing it.</property>
<signal name="activate" handler="new_layer_cb"/>
</object>
</child>
<!-- XXX for vectorlayer from current end -->
<!-- XXX for `reference layer` -->
<child>
<object class="GtkAction" id="NewReferenceLayerAbove">
<property name="label" translatable="yes" context="Menu→Layer (labels), Accel Editor (labels)">New Reference Layer…</property>
<property name="tooltip" translatable="yes" context="Accel Editor (descriptions)">Add a new reference image layer above the current layer. That layer only shows a image file of another location, cannot edit it.</property>
<signal name="activate" handler="import_layers_cb"/>
</object>
</child>
<child>
<object class="GtkAction" id="NewReferenceLayerBelow">
<property name="label" translatable="yes" context="Menu→Layer (labels), Accel Editor (labels)">New Reference Layer Below</property>
<property name="tooltip" translatable="yes" context="Accel Editor (descriptions)">Add a new reference image layer below the current layer. That layer only shows a image file of another location, cannot edit it.</property>
<signal name="activate" handler="import_layers_cb"/>
</object>
</child>
<!-- XXX for `reference layer` end -->
<child>
<object class="GtkAction" id="NewLayerGroupAbove">
<property name="icon-name">mypaint-layer-group-new-symbolic</property>
Oops, something went wrong.

0 comments on commit c49a25c

Please sign in to comment.
You can’t perform that action at this time.