Skip to content

Commit

Permalink
ReferenceImageLayer: Add new layer class.
Browse files Browse the repository at this point in the history
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 c49a25c
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 65 deletions.
20 changes: 3 additions & 17 deletions gui/document.py
Expand Up @@ -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

Expand All @@ -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)
Expand Down
49 changes: 37 additions & 12 deletions gui/filehandling.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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 = [
Expand All @@ -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:
Expand All @@ -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:
Expand Down
24 changes: 23 additions & 1 deletion gui/layerprops.glade
@@ -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">
Expand Down Expand Up @@ -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>
Expand Down
79 changes: 68 additions & 11 deletions gui/layerprops.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
2 changes: 2 additions & 0 deletions gui/layerswindow.xml
Expand Up @@ -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'/>
Expand Down
3 changes: 3 additions & 0 deletions gui/menu.xml
Expand Up @@ -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/>
Expand Down
26 changes: 26 additions & 0 deletions gui/resources.xml
Expand Up @@ -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>
Expand Down

0 comments on commit c49a25c

Please sign in to comment.