Commit c2f9b155 authored by sumpfralle's avatar sumpfralle

moved clipboard handling to a separate plugin


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@1115 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent 624c80ee
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy project-wide -->
<object class="GtkAction" id="PasteModelFromClipboard">
<property name="label">_Paste</property>
<property name="short_label">_Paste</property>
<property name="tooltip">Paste a model from clipboard</property>
<property name="stock_id">gtk-paste</property>
</object>
<object class="GtkAction" id="CopyModelToClipboard">
<property name="label">_Copy</property>
<property name="short_label">_Copy</property>
<property name="tooltip">Copy model to clipboard</property>
<property name="stock_id">gtk-copy</property>
</object>
</interface>
...@@ -4348,18 +4348,6 @@ upon interesting bugs and weird results.</property> ...@@ -4348,18 +4348,6 @@ upon interesting bugs and weird results.</property>
<property name="label">_Touch off and tool change</property> <property name="label">_Touch off and tool change</property>
<property name="short_label">_Touch off</property> <property name="short_label">_Touch off</property>
</object> </object>
<object class="GtkAction" id="CopyModelToClipboard">
<property name="label">_Copy</property>
<property name="short_label">_Copy</property>
<property name="tooltip">Copy model to clipboard</property>
<property name="stock_id">gtk-copy</property>
</object>
<object class="GtkAction" id="PasteModelFromClipboard">
<property name="label">_Paste</property>
<property name="short_label">_Paste</property>
<property name="tooltip">Paste a model from clipboard</property>
<property name="stock_id">gtk-paste</property>
</object>
<object class="GtkWindow" id="DummyWindow"> <object class="GtkWindow" id="DummyWindow">
<child> <child>
<object class="GtkVBox" id="vbox2"> <object class="GtkVBox" id="vbox2">
......
...@@ -36,7 +36,8 @@ from pycam.Geometry.Plane import Plane ...@@ -36,7 +36,8 @@ from pycam.Geometry.Plane import Plane
import pycam.Geometry.Path import pycam.Geometry.Path
import pycam.Utils.log import pycam.Utils.log
from pycam.Utils.locations import get_data_file_location, \ from pycam.Utils.locations import get_data_file_location, \
get_ui_file_location, get_external_program_location get_ui_file_location, get_external_program_location, \
get_all_program_locations
import pycam.Utils import pycam.Utils
from pycam.Geometry.utils import sqrt from pycam.Geometry.utils import sqrt
from pycam.Gui.OpenGLTools import ModelViewWindowGL from pycam.Gui.OpenGLTools import ModelViewWindowGL
...@@ -81,14 +82,6 @@ FILTER_MODEL = (("All supported model filetypes", ...@@ -81,14 +82,6 @@ FILTER_MODEL = (("All supported model filetypes",
FILTER_CONFIG = (("Config files", "*.conf"),) FILTER_CONFIG = (("Config files", "*.conf"),)
FILTER_EMC_TOOL = (("EMC tool files", "*.tbl"),) FILTER_EMC_TOOL = (("EMC tool files", "*.tbl"),)
CLIPBOARD_TARGETS = {
"dxf": ("image/vnd.dxf", ),
"ps": ("application/postscript", ),
"stl": ("application/sla", ),
"svg": ("image/x-inkscape-svg", "image/svg+xml"),
"filename_drag": ("text/uri-list", "text-plain"),
}
PREFERENCES_DEFAULTS = { PREFERENCES_DEFAULTS = {
"enable_ode": False, "enable_ode": False,
"boundary_mode": -1, "boundary_mode": -1,
...@@ -149,6 +142,7 @@ user's home directory on startup/shutdown""" ...@@ -149,6 +142,7 @@ user's home directory on startup/shutdown"""
POCKETING_TYPES = ["none", "holes", "enclosed"] POCKETING_TYPES = ["none", "holes", "enclosed"]
MAX_UNDO_STATES = 10 MAX_UNDO_STATES = 10
FILENAME_DRAG_TARGETS = ("text/uri-list", "text-plain")
# floating point color values are only available since gtk 2.16 # floating point color values are only available since gtk 2.16
GTK_COLOR_MAX = 65535.0 GTK_COLOR_MAX = 65535.0
...@@ -383,8 +377,6 @@ class ProjectGui(object): ...@@ -383,8 +377,6 @@ class ProjectGui(object):
("Toggle3DView", self.toggle_3d_view, None, "<Control><Shift>v"), ("Toggle3DView", self.toggle_3d_view, None, "<Control><Shift>v"),
("ToggleProcessPoolWindow", self.toggle_process_pool_window, None, None), ("ToggleProcessPoolWindow", self.toggle_process_pool_window, None, None),
("UndoButton", self._restore_undo_state, None, "<Control>z"), ("UndoButton", self._restore_undo_state, None, "<Control>z"),
("CopyModelToClipboard", self.copy_model_to_clipboard, None, "<Control>c"),
("PasteModelFromClipboard", self.paste_model_from_clipboard, None, "<Control>v"),
("HelpUserManual", self.show_help, "User_Manual", "F1"), ("HelpUserManual", self.show_help, "User_Manual", "F1"),
("HelpIntroduction", self.show_help, "Introduction", None), ("HelpIntroduction", self.show_help, "Introduction", None),
("HelpSupportedFormats", self.show_help, "SupportedFormats", None), ("HelpSupportedFormats", self.show_help, "SupportedFormats", None),
...@@ -437,8 +429,6 @@ class ProjectGui(object): ...@@ -437,8 +429,6 @@ class ProjectGui(object):
self.settings.register_event("boundary-updated", self.update_view) self.settings.register_event("boundary-updated", self.update_view)
# configure drag-n-drop for config files and models # configure drag-n-drop for config files and models
self.configure_drag_and_drop(self.window) self.configure_drag_and_drop(self.window)
self.clipboard = gtk.clipboard_get()
self.clipboard.connect("owner-change", self.update_clipboard_state)
# other events # other events
self.window.connect("destroy", self.destroy) self.window.connect("destroy", self.destroy)
# the settings window # the settings window
...@@ -1056,7 +1046,6 @@ class ProjectGui(object): ...@@ -1056,7 +1046,6 @@ class ProjectGui(object):
self.update_parallel_processes_settings() self.update_parallel_processes_settings()
self.update_model_type_related_controls() self.update_model_type_related_controls()
self.update_toolpath_related_controls() self.update_toolpath_related_controls()
self.update_clipboard_state()
def update_gcode_controls(self, widget=None): def update_gcode_controls(self, widget=None):
# path mode # path mode
...@@ -1109,9 +1098,6 @@ class ProjectGui(object): ...@@ -1109,9 +1098,6 @@ class ProjectGui(object):
z_low_control.set_sensitive(False) z_low_control.set_sensitive(False)
else: else:
z_low_control.set_sensitive(True) z_low_control.set_sensitive(True)
# copy button
self.gui.get_object("CopyModelToClipboard").set_sensitive(
bool(model and model.is_export_supported()))
def update_ode_settings(self, widget=None): def update_ode_settings(self, widget=None):
if pycam.Utils.threading.is_multiprocessing_enabled() \ if pycam.Utils.threading.is_multiprocessing_enabled() \
...@@ -1652,75 +1638,6 @@ class ProjectGui(object): ...@@ -1652,75 +1638,6 @@ class ProjectGui(object):
obj.set_value(default_value) obj.set_value(default_value)
self.gui.get_object("ExportEMCToolDefinition").set_sensitive(len(self.tool_list) > 0) self.gui.get_object("ExportEMCToolDefinition").set_sensitive(len(self.tool_list) > 0)
def update_clipboard_state(self, clipboard=None, event=None):
data, importer = self._get_data_and_importer_from_clipboard()
paste_button = self.gui.get_object("PasteModelFromClipboard")
paste_button.set_sensitive(not data is None)
def _copy_text_to_clipboard(self, text, targets):
clip_targets = [(key, gtk.TARGET_OTHER_WIDGET, index)
for index, key in enumerate(targets)]
def get_func(clipboard, selectiondata, info, (text, clip_type)):
selectiondata.set(clip_type, 8, text)
if "svg" in "".join(targets).lower():
# Inkscape for Windows strictly requires the BITMAP type
clip_type = gtk.gdk.SELECTION_TYPE_BITMAP
else:
clip_type = gtk.gdk.SELECTION_TYPE_STRING
result = self.clipboard.set_with_data(clip_targets, get_func,
lambda *args: None, (text, clip_type))
self.clipboard.store()
def copy_model_to_clipboard(self, widget=None):
# TODO: use all selected models (incl. merge?)
model = self.settings.get("models").get_selected()[0]
if not model.is_export_supported():
return
text_buffer = StringIO.StringIO()
model.export(comment=self.get_meta_data(),
unit=self.settings.get("unit")).write(text_buffer)
text_buffer.seek(0)
is_contour = isinstance(model, pycam.Geometry.Model.ContourModel)
# TODO: this should not be decided here
if is_contour:
targets = CLIPBOARD_TARGETS["svg"]
else:
targets = CLIPBOARD_TARGETS["stl"]
self._copy_text_to_clipboard(text_buffer.read(), targets)
def _get_data_and_importer_from_clipboard(self):
for targets, filename in ((CLIPBOARD_TARGETS["svg"], "foo.svg"),
(CLIPBOARD_TARGETS["stl"], "foo.stl"),
(CLIPBOARD_TARGETS["ps"], "foo.ps"),
(CLIPBOARD_TARGETS["dxf"], "foo.dxf")):
for target in targets:
data = self.clipboard.wait_for_contents(target)
if not data is None:
importer = pycam.Importers.detect_file_type(filename)[1]
return data, importer
return None, None
@gui_activity_guard
def paste_model_from_clipboard(self, widget=None):
data, importer = self._get_data_and_importer_from_clipboard()
progress = self.settings.get("progress")
if data:
progress.update(text="Loading model from clipboard")
text_buffer = StringIO.StringIO(data.data)
model = importer(text_buffer,
program_locations=self._get_program_locations(),
unit=self.settings.get("unit"),
fonts_cache=self.settings.get("fonts"),
callback=progress.update)
if model:
log.info("Loaded a model from clipboard")
self.load_model(model)
else:
log.warn("Failed to load a model from clipboard")
else:
log.warn("The clipboard does not contain suitable data")
progress.finish()
@gui_activity_guard @gui_activity_guard
def toggle_about_window(self, widget=None, event=None, state=None): def toggle_about_window(self, widget=None, event=None, state=None):
# only "delete-event" uses four arguments # only "delete-event" uses four arguments
...@@ -2344,7 +2261,7 @@ class ProjectGui(object): ...@@ -2344,7 +2261,7 @@ class ProjectGui(object):
obj.connect("drag-data-received", self.handle_data_drop) obj.connect("drag-data-received", self.handle_data_drop)
flags = gtk.DEST_DEFAULT_ALL flags = gtk.DEST_DEFAULT_ALL
targets = [(key, gtk.TARGET_OTHER_APP, index) targets = [(key, gtk.TARGET_OTHER_APP, index)
for index, key in enumerate(CLIPBOARD_TARGETS["filename_drag"])] for index, key in enumerate(FILENAME_DRAG_TARGETS)]
actions = gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | \ actions = gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | \
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE | \ gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE | \
gtk.gdk.ACTION_ASK gtk.gdk.ACTION_ASK
...@@ -2391,15 +2308,6 @@ class ProjectGui(object): ...@@ -2391,15 +2308,6 @@ class ProjectGui(object):
uri = widget.get_current_uri() uri = widget.get_current_uri()
self.load_model_file(filename=uri) self.load_model_file(filename=uri)
def _get_program_locations(self):
# import all external program locations into a dict
program_locations = {}
prefix = "external_program_"
for key in self.settings.get_keys():
if key.startswith(prefix) and self.settings.get(key):
program_locations[key[len(prefix):]] = self.settings.get(key)
return program_locations
@gui_activity_guard @gui_activity_guard
def load_model_file(self, widget=None, filename=None, store_filename=True): def load_model_file(self, widget=None, filename=None, store_filename=True):
if callable(filename): if callable(filename):
...@@ -2415,7 +2323,7 @@ class ProjectGui(object): ...@@ -2415,7 +2323,7 @@ class ProjectGui(object):
# "cancel" is not allowed # "cancel" is not allowed
progress.disable_cancel() progress.disable_cancel()
if self.load_model(importer(filename, if self.load_model(importer(filename,
program_locations=self._get_program_locations(), program_locations=get_all_program_locations(self.settings),
unit=self.settings.get("unit"), unit=self.settings.get("unit"),
fonts_cache=self.settings.get("fonts"), fonts_cache=self.settings.get("fonts"),
callback=progress.update)): callback=progress.update)):
......
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2011 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import StringIO
# imported later (on demand)
#import gtk
import pycam.Plugins
from pycam.Utils.locations import get_all_program_locations
CLIPBOARD_TARGETS = {
"dxf": ("image/vnd.dxf", ),
"ps": ("application/postscript", ),
"stl": ("application/sla", ),
"svg": ("image/x-inkscape-svg", "image/svg+xml"),
}
class Clipboard(pycam.Plugins.PluginBase):
UI_FILE = "clipboard.ui"
DEPENDS = ["Models"]
def setup(self):
if self.gui:
import gtk
self._gtk = gtk
self.clipboard = self._gtk.clipboard_get()
self.core.set("clipboard-set", self._copy_text_to_clipboard)
self.clipboard.connect("owner-change", self._update_clipboard_widget)
self.core.register_event("model-selection-changed",
self._update_clipboard_widget)
# menu item and shortcut
actiongroup = self._gtk.ActionGroup("clipboard")
for objname, func, hotkey in (
("CopyModelToClipboard", self.copy_model_to_clipboard, "<Control>c"),
("PasteModelFromClipboard", self.paste_model_from_clipboard, "<Control>v")):
action = self.gui.get_object(objname)
action.connect("activate", func)
key, mod = self._gtk.accelerator_parse(hotkey)
# TODO: move the "<pycam>" accel path somewhere else
accel_path = "<pycam>/%s" % objname
action.set_accel_path(accel_path)
self._gtk.accel_map_change_entry(accel_path, key, mod, True)
actiongroup.add_action(action)
self.core.get("gtk-uimanager").insert_action_group(actiongroup, pos=-1)
self._update_clipboard_widget()
return True
def _get_exportable_models(self):
models = self.core.get("models").get_selected()
exportable = []
for model in models:
if model.is_export_supported():
exportable.append(model)
return exportable
def _update_clipboard_widget(self, widget=None, data=None):
models = self._get_exportable_models()
# copy button
self.gui.get_object("CopyModelToClipboard").set_sensitive(
len(models) > 0)
data, importer = self._get_data_and_importer_from_clipboard()
paste_button = self.gui.get_object("PasteModelFromClipboard")
paste_button.set_sensitive(not data is None)
def _copy_text_to_clipboard(self, text, targets=None):
if targets is None:
self.clipboard.set_text(text)
else:
if targets in CLIPBOARD_TARGETS:
targets = CLIPBOARD_TARGETS[targets]
clip_targets = [(key, self._gtk.TARGET_OTHER_WIDGET, index)
for index, key in enumerate(targets)]
def get_func(clipboard, selectiondata, info, (text, clip_type)):
selectiondata.set(clip_type, 8, text)
if "svg" in "".join(targets).lower():
# Inkscape for Windows strictly requires the BITMAP type
clip_type = self._gtk.gdk.SELECTION_TYPE_BITMAP
else:
clip_type = self._gtk.gdk.SELECTION_TYPE_STRING
result = self.clipboard.set_with_data(clip_targets, get_func,
lambda *args: None, (text, clip_type))
self.clipboard.store()
def copy_model_to_clipboard(self, widget=None):
models = self._get_exportable_models()
if not models:
return
text_buffer = StringIO.StringIO()
# TODO: use a better way to discover the "merge" ability
def same_type(m1, m2):
return isinstance(m1, pycam.Geometry.Model.ContourModel) == \
isinstance(m2, pycam.Geometry.Model.ContourModel)
merged_model = models.pop(0)
for model in models:
# merge only 3D _or_ 2D models (don't mix them)
if same_type(merged_model, model):
merged_model += model
# TODO: add "comment=get_meta_data()" here
merged_model.export(unit=self.core.get("unit")).write(text_buffer)
text_buffer.seek(0)
is_contour = isinstance(merged_model, pycam.Geometry.Model.ContourModel)
# TODO: this should not be decided here
if is_contour:
targets = CLIPBOARD_TARGETS["svg"]
else:
targets = CLIPBOARD_TARGETS["stl"]
self._copy_text_to_clipboard(text_buffer.read(), targets)
def _get_data_and_importer_from_clipboard(self):
for targets, filename in ((CLIPBOARD_TARGETS["svg"], "foo.svg"),
(CLIPBOARD_TARGETS["stl"], "foo.stl"),
(CLIPBOARD_TARGETS["ps"], "foo.ps"),
(CLIPBOARD_TARGETS["dxf"], "foo.dxf")):
for target in targets:
data = self.clipboard.wait_for_contents(target)
if not data is None:
importer = pycam.Importers.detect_file_type(filename)[1]
return data, importer
return None, None
def paste_model_from_clipboard(self, widget=None):
data, importer = self._get_data_and_importer_from_clipboard()
progress = self.core.get("progress")
if data:
progress.update(text="Loading model from clipboard")
text_buffer = StringIO.StringIO(data.data)
model = importer(text_buffer,
program_locations=get_all_program_locations(self.core),
unit=self.core.get("unit"),
fonts_cache=self.core.get("fonts"),
callback=progress.update)
if model:
self.log.info("Loaded a model from clipboard")
self.core.get("load_model")(model)
else:
self.log.warn("Failed to load a model from clipboard")
else:
self.log.warn("The clipboard does not contain suitable data")
progress.finish()
...@@ -21,6 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,6 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
import os import os
import StringIO
# imported later (on demand) # imported later (on demand)
#import gtk #import gtk
...@@ -34,6 +35,7 @@ from pycam.Geometry.Letters import TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, \ ...@@ -34,6 +35,7 @@ from pycam.Geometry.Letters import TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, \
class Fonts(pycam.Plugins.PluginBase): class Fonts(pycam.Plugins.PluginBase):
UI_FILE = "fonts.ui" UI_FILE = "fonts.ui"
DEPENDS = ["Clipboard"]
def setup(self): def setup(self):
self._fonts_cache = pycam.Utils.FontCache.FontCache(get_font_dir(), self._fonts_cache = pycam.Utils.FontCache.FontCache(get_font_dir(),
...@@ -113,7 +115,7 @@ class Fonts(pycam.Plugins.PluginBase): ...@@ -113,7 +115,7 @@ class Fonts(pycam.Plugins.PluginBase):
if sorted_keys: if sorted_keys:
font_selector.set_active(0) font_selector.set_active(0)
else: else:
log.warn("No single-line fonts found!") self.log.warn("No single-line fonts found!")
font_selector.connect("changed", font_selector.connect("changed",
self.update_font_dialog_preview) self.update_font_dialog_preview)
font_selector.show() font_selector.show()
...@@ -126,7 +128,7 @@ class Fonts(pycam.Plugins.PluginBase): ...@@ -126,7 +128,7 @@ class Fonts(pycam.Plugins.PluginBase):
self.font_dialog_window.show() self.font_dialog_window.show()
self._font_dialog_window_visible = True self._font_dialog_window_visible = True
else: else:
log.error("No fonts were found on your system. " \ self.log.error("No fonts were found on your system. " \
+ "Please check the Log Window for details.") + "Please check the Log Window for details.")
else: else:
self._font_dialog_window_position = \ self._font_dialog_window_position = \
...@@ -176,11 +178,12 @@ class Fonts(pycam.Plugins.PluginBase): ...@@ -176,11 +178,12 @@ class Fonts(pycam.Plugins.PluginBase):
text_model = self.get_font_dialog_text_rendered() text_model = self.get_font_dialog_text_rendered()
if text_model and (not text_model.maxx is None): if text_model and (not text_model.maxx is None):
text_buffer = StringIO.StringIO() text_buffer = StringIO.StringIO()
text_model.export(comment=self.get_meta_data(), # TODO: add "comment=get_meta_data()"
unit=self.settings.get("unit")).write(text_buffer) text_model.export(unit=self.core.get("unit")).write(text_buffer)
text_buffer.seek(0) text_buffer.seek(0)
text = text_buffer.read() text = text_buffer.read()
self._copy_text_to_clipboard(text, CLIPBOARD_TARGETS["svg"]) self.core.get("clipboard-set")(text,
targets="svg")
def update_font_dialog_preview(self, widget=None, event=None): def update_font_dialog_preview(self, widget=None, event=None):
if not self.font_selector: if not self.font_selector:
......
...@@ -30,6 +30,7 @@ import pycam.Plugins ...@@ -30,6 +30,7 @@ import pycam.Plugins
class Log(pycam.Plugins.PluginBase): class Log(pycam.Plugins.PluginBase):
UI_FILE = "log.ui" UI_FILE = "log.ui"
DEPENDS = ["Clipboard"]
def setup(self): def setup(self):
if self.gui: if self.gui:
...@@ -103,7 +104,7 @@ class Log(pycam.Plugins.PluginBase): ...@@ -103,7 +104,7 @@ class Log(pycam.Plugins.PluginBase):
columns.append(model.get_value(it, column)) columns.append(model.get_value(it, column))
content.append(" ".join(columns)) content.append(" ".join(columns))
self.log_model.foreach(copy_row, content) self.log_model.foreach(copy_row, content)
self.clipboard.set_text(os.linesep.join(content)) self.core.get("clipboard-set")(os.linesep.join(content))
self.gui.get_object("StatusBarWarning").hide() self.gui.get_object("StatusBarWarning").hide()
def clear_log_window(self, widget=None): def clear_log_window(self, widget=None):
......
...@@ -136,3 +136,13 @@ def get_external_program_location(key): ...@@ -136,3 +136,13 @@ def get_external_program_location(key):
# nothing found # nothing found
return None return None
def get_all_program_locations(core):
# TODO: this should move to a plugin
# import all external program locations into a dict
program_locations = {}
prefix = "external_program_"
for key in core.get_keys():
if key.startswith(prefix) and core.get(key):
program_locations[key[len(prefix):]] = core.get(key)
return program_locations
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment