WIP, having some bugs with the 3D view still

This commit is contained in:
Suzanne Soy 2021-09-30 23:39:02 +01:00
parent f29abe0813
commit cca8e98d11
3 changed files with 903 additions and 410 deletions

View File

@ -1,13 +1,115 @@
if True: import os
from PySide import QtGui import FreeCAD as App
import FreeCADGui
from PySide import QtGui
from PySide import QtCore
def toolbarAction(act): """
from SearchTools import SearchTools
from importlib import reload
reload(SearchTools)
TODO for this project:
* find a way to use the FreeCAD 3D viewer without segfaults or disappearing widgets
* fix sync problem when moving too fast
* split the list of tools vs. document objects (possibly already done?)
* save to disk the list of tools
* always display including when switching workbenches
"""
################################""
class SafeViewer(QtGui.QWidget):
"""FreeCAD uses a modified version of QuarterWidget, so the import pivy.quarter one will cause segfaults.
FreeCAD's FreeCADGui.createViewer() puts the viewer widget inside an MDI window, and detaching it without causing segfaults on exit is tricky.
This class contains some kludges to extract the viewer as a standalone widget and destroy it safely."""
def __init__(self, parent = None):
print('init')
super(SafeViewer, self).__init__()
self.viewer = FreeCADGui.createViewer()
self.graphicsView = self.viewer.graphicsView()
self.oldGraphicsViewParent = self.graphicsView.parent()
self.oldGraphicsViewParentParent = self.oldGraphicsViewParent.parent()
self.oldGraphicsViewParentParentParent = self.oldGraphicsViewParentParent.parent()
# Avoid segfault but still hide the undesired window by moving it to a new hidden MDI area.
self.hiddenQMDIArea = QtGui.QMdiArea()
self.hiddenQMDIArea.addSubWindow(self.oldGraphicsViewParentParentParent)
self.private_widget = self.oldGraphicsViewParent
self.private_widget.setParent(parent)
self.setLayout(QtGui.QVBoxLayout())
self.layout().addWidget(self.private_widget)
def fin(slf):
slf.finalizer()
import weakref
weakref.finalize(self, fin, self)
self.destroyed.connect(self.finalizer)
def finalizer(self):
print('fin')
# Cleanup in an order that doesn't cause a segfault:
self.private_widget.setParent(self.oldGraphicsViewParentParent)
self.oldGraphicsViewParentParentParent.close()
self.oldGraphicsViewParentParentParent = None
self.oldGraphicsViewParentParent = None
self.oldGraphicsViewParent = None
self.graphicsView = None
self.viewer = None
#self.parent = None
self.hiddenQMDIArea = None
"""
# Example use:
from PySide import QtGui
import pivy
def mk(v):
w = QtGui.QMainWindow()
oldFocus = QtGui.QApplication.focusWidget()
sv.widget.setParent(w)
oldFocus.setFocus()
w.show()
col = pivy.coin.SoBaseColor()
col.rgb = (1, 0, 0)
trans = pivy.coin.SoTranslation()
trans.translation.setValue([0, 0, 0])
cub = pivy.coin.SoCube()
myCustomNode = pivy.coin.SoSeparator()
myCustomNode.addChild(col)
myCustomNode.addChild(trans)
myCustomNode.addChild(cub)
sv.viewer.getViewer().setSceneGraph(myCustomNode)
sv.viewer.fitAll()
return w
sv = SafeViewer()
ww=mk(sv)
"""
genericToolIcon = QtGui.QIcon(QtGui.QIcon(os.path.dirname(__file__) + '/Tango-Tools-spanner-hammer.svg'))
def iconToBase64(icon, sz = QtCore.QSize(64,64), mode = QtGui.QIcon.Mode.Normal, state = QtGui.QIcon.State.On):
buf = QtCore.QBuffer()
buf.open(QtCore.QIODevice.WriteOnly)
icon.pixmap(sz, mode, state).save(buf, 'PNG')
return QtCore.QTextCodec.codecForName('UTF-8').toUnicode(buf.data().toBase64())
def iconToHTML(icon, sz = 12, mode = QtGui.QIcon.Mode.Normal, state = QtGui.QIcon.State.On):
return '<img width="'+str(sz)+'" height="'+str(sz)+'" src="data:image/png;base64,' + iconToBase64(icon, QtCore.QSize(sz,sz), mode, state) + '" />'
def refreshToolsAction(act):
print('Refresh list of tools')
refreshToolbars()
def toolbarAction(act):
print('show toolbar ' + act['toolbar'] + ' from workbenches ' + repr(act['workbenches'])) print('show toolbar ' + act['toolbar'] + ' from workbenches ' + repr(act['workbenches']))
def subToolAction(act): def subToolAction(act):
toolPath = act['toolbar'] + '.' + act['tool'] toolPath = act['toolbar'] + '.' + act['tool']
if 'subTool' in act: if 'subTool' in act:
toolPath = toolPath + '.' + act['subTool'] toolPath = toolPath + '.' + act['subTool']
def runTool(): def runTool():
mw = Gui.getMainWindow()
for the_toolbar in mw.findChildren(QtGui.QToolBar, act['toolbar']): for the_toolbar in mw.findChildren(QtGui.QToolBar, act['toolbar']):
for tbt in the_toolbar.findChildren(QtGui.QToolButton): for tbt in the_toolbar.findChildren(QtGui.QToolButton):
if tbt.text() == act['tool']: if tbt.text() == act['tool']:
@ -36,30 +138,109 @@ if True:
else: else:
for workbench in act['workbenches']: for workbench in act['workbenches']:
print('Activating workbench ' + workbench + ' to access tool ' + toolPath) print('Activating workbench ' + workbench + ' to access tool ' + toolPath)
Gui.activateWorkbench(workbench) FreeCADGui.activateWorkbench(workbench)
if runTool(): if runTool():
return return
print('Tool ' + toolPath + ' not found, was it offered by an extension that is no longer present?') print('Tool ' + toolPath + ' not found, was it offered by an extension that is no longer present?')
def documentObjectAction(act): def documentObjectAction(act):
print('select object ' + act['document'] + '.' + act['object']) print('select object ' + act['document'] + '.' + act['object'])
Gui.Selection.addSelection(act['document'], act['object']) FreeCADGui.Selection.addSelection(act['document'], act['object'])
def documentAction(act): def documentAction(act):
# Todo: this should also select the document in the tree view # Todo: this should also select the document in the tree view
print('switch to document ' + act['document']) print('switch to document ' + act['document'])
App.setActiveDocument(act['document']) App.setActiveDocument(act['document'])
actionHandlers = { actionHandlers = {
'toolbarAction': toolbarAction, 'refreshTools': refreshToolsAction,
'toolAction': subToolAction, 'toolbar': toolbarAction,
'subToolAction': subToolAction, 'tool': subToolAction,
'documentObjectAction': documentObjectAction, 'subTool': subToolAction,
'documentAction': documentAction 'documentObject': documentObjectAction,
} 'document': documentAction
}
# Inspired by https://stackoverflow.com/a/5443220/324969 # For some reason, the viewer always works except when used for two consecutive items in the search results: it then disappears after a short zoom-in+zoom-out animation.
from PySide import QtGui # I'm giving up on getting this viewer to work in a clean way, and will try swapping two instances so that the same one is never used twice in a row.
# safeViewerInstanceA = None
# Inspired by https://forum.qt.io/topic/69807/qtreeview-indent-entire-row safeViewerInstanceB = None
class IndentedItemDelegate(QtGui.QStyledItemDelegate):
import pivy
class DocumentObjectToolTipWidget(QtGui.QWidget):
def __init__(self, nfo):
super(DocumentObjectToolTipWidget, self).__init__()
html = '<p>' + nfo['toolTip']['label'] + '</p><p><code>App.getDocument(' + repr(str(nfo['toolTip']['docName'])) + ').getObject(' + repr(str(nfo['toolTip']['name'])) + ')</code></p>'
description = QtGui.QTextEdit()
description.setReadOnly(True)
description.setAlignment(QtCore.Qt.AlignTop)
description.setText(html)
global safeViewerInstanceA, safeViewerInstanceB
if safeViewerInstanceA is None:
oldFocus = QtGui.QApplication.focusWidget()
safeViewerInstanceA = SafeViewer()
safeViewerInstanceB = SafeViewer()
oldFocus.setFocus()
# Tried setting the preview to a fixed size to prevent it from disappearing when changing its contents, this sets it to a fixed size but doesn't actually pick the size, .resize does that but isn't enough to fix the bug.
#safeViewerInstance.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed))
self.preview = safeViewerInstanceA
safeViewerInstanceA, safeViewerInstanceB = safeViewerInstanceB, safeViewerInstanceA
obj = App.getDocument(str(nfo['toolTip']['docName'])).getObject(str(nfo['toolTip']['name']))
## dummy preview:
#col = pivy.coin.SoBaseColor()
#col.rgb = (1, 0, 0)
#trans = pivy.coin.SoTranslation()
#trans.translation.setValue([0, 0, 0])
#cub = pivy.coin.SoCube()
#myCustomNode = pivy.coin.SoSeparator()
#myCustomNode.addChild(col)
#myCustomNode.addChild(trans)
#myCustomNode.addChild(cub)
#self.preview.viewer.getViewer().setSceneGraph(myCustomNode)
# Tried hiding/detaching the preview to prevent it from disappearing when changing its contents
self.preview.viewer.stopAnimating()
self.preview.viewer.getViewer().setSceneGraph(obj.ViewObject.RootNode)
self.preview.viewer.fitAll()
lay = QtGui.QVBoxLayout()
self.setLayout(lay)
lay.addWidget(description)
lay.addWidget(self.preview)
def finalizer(self):
#self.preview.finalizer()
# Detach the widget so that it may be reused without getting deleted
self.preview.setParent(None)
def easyToolTipWidget(html):
foo = QtGui.QTextEdit()
foo.setReadOnly(True)
foo.setAlignment(QtCore.Qt.AlignTop)
foo.setText(html)
return foo
def refreshToolsToolTip(nfo):
return easyToolTipWidget(iconToHTML(genericToolIcon) + '<p>Load all workbenches to refresh this list of tools. This may take a minute, depending on the number of installed workbenches.</p>')
def toolbarToolTip(nfo):
return easyToolTipWidget('<p>Display toolbar ' + nfo['toolTip'] + '</p><p>This toolbar appears in the following workbenches: <ul>' + ''.join(['<li>' + iconToHTML(QtGui.QIcon(FreeCADGui.listWorkbenches()[wb].Icon)) + wb + '</li>' for wb in nfo['action']['workbenches']]) + '</ul></p>')
def subToolToolTip(nfo):
return easyToolTipWidget(iconToHTML(nfo['icon'], 32) + '<p>' + nfo['toolTip'] + '</p>')
def documentObjectToolTip(nfo):
return DocumentObjectToolTipWidget(nfo)
def documentToolTip(nfo):
return easyToolTipWidget('<p>' + nfo['toolTip']['label'] + '</p><p><code>App.getDocument(' + repr(str(nfo['toolTip']['name'])) + ')</code></p><p><img src="data:image/png;base64,.............."></p>')
toolTipHandlers = {
'refreshTools': refreshToolsToolTip,
'toolbar': toolbarToolTip,
'tool': subToolToolTip,
'subTool': subToolToolTip,
'documentObject': documentObjectToolTip,
'document': documentToolTip
}
# Inspired by https://stackoverflow.com/a/5443220/324969
# Inspired by https://forum.qt.io/topic/69807/qtreeview-indent-entire-row
class IndentedItemDelegate(QtGui.QStyledItemDelegate):
def __init__(self): def __init__(self):
super(IndentedItemDelegate, self).__init__() super(IndentedItemDelegate, self).__init__()
def paint(self, painter, option, index): def paint(self, painter, option, index):
@ -67,8 +248,8 @@ if True:
indent = 16 * depth indent = 16 * depth
option.rect.adjust(indent, 0, 0, 0) option.rect.adjust(indent, 0, 0, 0)
super(IndentedItemDelegate, self).paint(painter, option, index) super(IndentedItemDelegate, self).paint(painter, option, index)
# #
class SearchBox(QtGui.QLineEdit): class SearchBox(QtGui.QLineEdit):
resultSelected = QtCore.Signal(int, str) resultSelected = QtCore.Signal(int, str)
def __init__(self, itemGroups, itemDelegate = IndentedItemDelegate(), maxVisibleRows = 20, parent = None): def __init__(self, itemGroups, itemDelegate = IndentedItemDelegate(), maxVisibleRows = 20, parent = None):
# Call parent cosntructor # Call parent cosntructor
@ -79,6 +260,8 @@ if True:
self.maxVisibleRows = maxVisibleRows # TODO: use this to compute the correct height self.maxVisibleRows = maxVisibleRows # TODO: use this to compute the correct height
# Create proxy model # Create proxy model
self.proxyModel = QtCore.QIdentityProxyModel() self.proxyModel = QtCore.QIdentityProxyModel()
# Filtered model to which items are manually added. Store it as a property of the object instead of a local variable, to prevent grbage collection.
self.mdl = QtGui.QStandardItemModel()
#self.proxyModel.setModel(self.model) #self.proxyModel.setModel(self.model)
# Create list view # Create list view
self.listView = QtGui.QListView(self) self.listView = QtGui.QListView(self)
@ -91,11 +274,11 @@ if True:
self.listView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.listView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
# Create pane for showing extra info about the currently-selected tool # Create pane for showing extra info about the currently-selected tool
#self.extraInfo = QtGui.QLabel() #self.extraInfo = QtGui.QLabel()
self.extraInfo = QtGui.QTextEdit() self.extraInfo = QtGui.QWidget()
self.extraInfo.setReadOnly(True)
self.extraInfo.setWindowFlags(QtGui.Qt.ToolTip) self.extraInfo.setWindowFlags(QtGui.Qt.ToolTip)
self.extraInfo.setWindowFlag(QtGui.Qt.FramelessWindowHint) self.extraInfo.setWindowFlag(QtGui.Qt.FramelessWindowHint)
self.extraInfo.setAlignment(QtCore.Qt.AlignTop) self.extraInfo.setLayout(QtGui.QVBoxLayout())
self.extraInfo.layout().setContentsMargins(0,0,0,0)
# Connect signals and slots # Connect signals and slots
self.textChanged.connect(self.filterModel) self.textChanged.connect(self.filterModel)
self.listView.clicked.connect(lambda x: self.selectResult('select', x)) self.listView.clicked.connect(lambda x: self.selectResult('select', x))
@ -122,6 +305,8 @@ if True:
QtCore.Qt.Key_Up: lambda current, nbRows: (current - 1) % nbRows, QtCore.Qt.Key_Up: lambda current, nbRows: (current - 1) % nbRows,
QtCore.Qt.Key_PageDown: lambda current, nbRows: min(current + max(1, self.maxVisibleRows / 2), nbRows - 1), QtCore.Qt.Key_PageDown: lambda current, nbRows: min(current + max(1, self.maxVisibleRows / 2), nbRows - 1),
QtCore.Qt.Key_PageUp: lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0), QtCore.Qt.Key_PageUp: lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0),
QtCore.Qt.Key_Home: lambda current, nbRows: 0,
QtCore.Qt.Key_End: lambda current, nbRows: nbRows - 1,
} }
acceptKeys = { acceptKeys = {
QtCore.Qt.Key_Enter: 'select', QtCore.Qt.Key_Enter: 'select',
@ -139,6 +324,7 @@ if True:
if self.listView.isEnabled(): if self.listView.isEnabled():
currentRow = currentIndex.row() currentRow = currentIndex.row()
nbRows = self.listView.model().rowCount() nbRows = self.listView.model().rowCount()
if nbRows > 0:
newRow = listMovementKeys[key](currentRow, nbRows) newRow = listMovementKeys[key](currentRow, nbRows)
index = self.listView.model().index(newRow, 0) index = self.listView.model().index(newRow, 0)
self.listView.setCurrentIndex(index) self.listView.setCurrentIndex(index)
@ -179,23 +365,23 @@ if True:
else: else:
subitems = filterGroups(group['subitems']) subitems = filterGroups(group['subitems'])
if len(subitems) > 0 or matches(group['text']): if len(subitems) > 0 or matches(group['text']):
return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'toolTipHTML':group['toolTipHTML'], 'subitems': subitems } return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'toolTip':group['toolTip'], 'subitems': subitems }
else: else:
return None return None
def filterGroups(groups): def filterGroups(groups):
groups = (filterGroup(group) for group in groups) groups = (filterGroup(group) for group in groups)
return [group for group in groups if group is not None] return [group for group in groups if group is not None]
self.mdl = QtGui.QStandardItemModel()
self.mdl.appendColumn([])
def addGroups(filteredGroups, depth=0): def addGroups(filteredGroups, depth=0):
for group in filteredGroups: for group in filteredGroups:
mdl.appendRow([QtGui.QStandardItem(group['icon'] or QtGui.QIcon(), group['text']), self.mdl.appendRow([QtGui.QStandardItem(group['icon'] or genericToolIcon, group['text']),
QtGui.QStandardItem(str(depth)), QtGui.QStandardItem(str(depth)),
QtGui.QStandardItem(group['action']), QtGui.QStandardItem(group['action']),
QtGui.QStandardItem(group['toolTipHTML'])]) QtGui.QStandardItem(json.dumps(serializeItemGroup(group)))])
addGroups(group['subitems'], depth+1) addGroups(group['subitems'], depth+1)
mdl = QtGui.QStandardItemModel()
mdl.appendColumn([])
addGroups(filterGroups(self.itemGroups)) addGroups(filterGroups(self.itemGroups))
self.proxyModel.setSourceModel(mdl) self.proxyModel.setSourceModel(self.mdl)
# TODO: try to find the already-highlighted item # TODO: try to find the already-highlighted item
nbRows = self.listView.model().rowCount() nbRows = self.listView.model().rowCount()
if nbRows > 0: if nbRows > 0:
@ -219,7 +405,7 @@ if True:
hint_w = self.listView.sizeHint().width() hint_w = self.listView.sizeHint().width()
# TODO: this can still bump into the bottom of the screen, in that case we should flip # TODO: this can still bump into the bottom of the screen, in that case we should flip
w = max(siz.width(), hint_w) w = max(siz.width(), hint_w)
h = 100 h = 200 # TODO: set height / size here according to desired number of items
extraw = w # choose a preferred width that doesn't change all the time, extraw = w # choose a preferred width that doesn't change all the time,
# self.extraInfo.sizeHint().width() would change for every item. # self.extraInfo.sizeHint().width() would change for every item.
extrax = x - extraw extrax = x - extraw
@ -252,25 +438,36 @@ if True:
elif len(deselected) > 0: elif len(deselected) > 0:
self.hideExtraInfo() self.hideExtraInfo()
def setExtraInfo(self, index): def setExtraInfo(self, index):
toolTipHTML = str(index.model().itemData(index.siblingAtColumn(3))[0]) nfo = str(index.model().itemData(index.siblingAtColumn(3))[0])
self.extraInfo.setText(toolTipHTML) # TODO: move this outside of this class, probably use a single metadata
metadata = str(index.model().itemData(index.siblingAtColumn(2))[0])
nfo = deserializeItemGroup(json.loads(nfo))
nfo['action'] = json.loads(nfo['action'])
toolTipWidget = toolTipHandlers[nfo['action']['handler']](nfo)
#while len(self.extraInfo.children()) > 0:
# self.extraInfo.children()[0].setParent(None)
w = self.extraInfo.layout().takeAt(0)
while w:
if hasattr(w.widget(), 'finalizer'):
# The 3D viewer segfaults very easily if it is used after being destroyed, and some Python/C++ interop seems to overzealously destroys some widgets, including this one, too soon?
# Ensuring that we properly detacth the 3D viewer widget before discarding its parent seems to avoid these crashes.
w.widget().finalizer()
w.widget().setParent(None)
w = self.extraInfo.layout().takeAt(0)
self.extraInfo.layout().addWidget(toolTipWidget)
global toto
toto = self.extraInfo
#toolTipHTML = toolTipHandlers[nfo['action']['handler']](nfo)
#self.extraInfo.setText(toolTipHTML)
self.setFloatingWidgetsGeometry() self.setFloatingWidgetsGeometry()
def clearExtraInfo(self, index): def clearExtraInfo(self):
self.extraInfo.setText('') self.extraInfo.setText('')
def showExtraInfo(self): def showExtraInfo(self):
self.extraInfo.show() self.extraInfo.show()
mw = Gui.getMainWindow() def getAllToolbars():
mdi = mw.findChild(QtGui.QMdiArea)
wdg = QtGui.QWidget()
lay = QtGui.QGridLayout(wdg)
mwx = QtGui.QMainWindow()
mbr = mw.findChildren(QtGui.QToolBar, 'File')[0]
def getAllToolbars():
all_tbs = dict() all_tbs = dict()
for wbname, workbench in Gui.listWorkbenches().items(): for wbname, workbench in FreeCADGui.listWorkbenches().items():
try: try:
tbs = workbench.listToolbars() tbs = workbench.listToolbars()
except: except:
@ -282,8 +479,8 @@ if True:
all_tbs[tb].add(wbname) all_tbs[tb].add(wbname)
return all_tbs return all_tbs
import json import json
def serializeIcon(icon): def serializeIcon(icon):
iconPixmaps = {} iconPixmaps = {}
for sz in icon.availableSizes(): for sz in icon.availableSizes():
strW = str(sz.width()) strW = str(sz.width())
@ -293,22 +490,19 @@ if True:
for strMode, mode in {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}.items(): for strMode, mode in {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}.items():
iconPixmaps[strW][strH][strMode] = {} iconPixmaps[strW][strH][strMode] = {}
for strState, state in {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}.items(): for strState, state in {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}.items():
buf = QtCore.QBuffer() iconPixmaps[strW][strH][strMode][strState] = iconToBase64(icon, sz, mode, state)
buf.open(QtCore.QIODevice.WriteOnly)
icon.pixmap(sz, mode, state).save(buf, 'PNG')
iconPixmaps[strW][strH][strMode][strState] = QtCore.QTextCodec.codecForName('UTF-8').toUnicode(buf.data().toBase64())
return iconPixmaps return iconPixmaps
# workbenches is a list(str), toolbar is a str, text is a str, icon is a QtGui.QIcon # workbenches is a list(str), toolbar is a str, text is a str, icon is a QtGui.QIcon
def serializeTool(tool): def serializeTool(tool):
return { return {
'workbenches': tool['workbenches'], 'workbenches': tool['workbenches'],
'toolbar': tool['toolbar'], 'toolbar': tool['toolbar'],
'text': tool['text'], 'text': tool['text'],
'toolTip': tool['toolTip'],
'icon': serializeIcon(tool['icon']), 'icon': serializeIcon(tool['icon']),
'toolTipHTML': tool['toolTipHTML']
} }
def deserializeIcon(iconPixmaps): def deserializeIcon(iconPixmaps):
ico = QtGui.QIcon() ico = QtGui.QIcon()
for strW, wPixmaps in iconPixmaps.items(): for strW, wPixmaps in iconPixmaps.items():
for strH, hPixmaps in wPixmaps.items(): for strH, hPixmaps in wPixmaps.items():
@ -321,21 +515,26 @@ if True:
ico.addPixmap(pxm, mode, state) ico.addPixmap(pxm, mode, state)
return ico return ico
def deserializeTool(tool): def deserializeTool(tool):
return { return {
'workbenches': tool['workbenches'], 'workbenches': tool['workbenches'],
'toolbar': tool['toolbar'], 'toolbar': tool['toolbar'],
'text': tool['text'], 'text': tool['text'],
'toolTip': tool['toolTip'],
'icon': deserializeIcon(tool['icon']), 'icon': deserializeIcon(tool['icon']),
'toolTipHTML': tool['toolTipHTML']
} }
# TODO: save serialized tools in App.getUserAppDataDir() ################################################################################################################ def GatherTools():
# + never cache the document objects
def GatherTools():
itemGroups = [] itemGroups = []
itemGroups.append({
'icon': genericToolIcon,
'text': 'Refresh list of tools',
'toolTip': '',
'action': json.dumps({'handler': 'refreshTools'}),
'subitems': []
})
all_tbs = getAllToolbars() all_tbs = getAllToolbars()
mw = FreeCADGui.getMainWindow()
for toolbarName, toolbarIsInWorkbenches in all_tbs.items(): for toolbarName, toolbarIsInWorkbenches in all_tbs.items():
toolbarIsInWorkbenches = sorted(list(toolbarIsInWorkbenches)) toolbarIsInWorkbenches = sorted(list(toolbarIsInWorkbenches))
for the_toolbar in mw.findChildren(QtGui.QToolBar, toolbarName): for the_toolbar in mw.findChildren(QtGui.QToolBar, toolbarName):
@ -352,17 +551,17 @@ if True:
subgroup = [] subgroup = []
for mac in men.actions(): for mac in men.actions():
if mac.text(): if mac.text():
action = { 'handler': 'subToolAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'subTool': mac.text() } action = { 'handler': 'subTool', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'subTool': mac.text() }
subgroup.append({'icon':mac.icon(), 'text':mac.text(), 'toolTipHTML': mac.toolTip(), 'action':json.dumps(action), 'subitems':[]}) subgroup.append({'icon':mac.icon(), 'text':mac.text(), 'toolTip': mac.toolTip(), 'action':json.dumps(action), 'subitems':[]})
# The default action of a menu changes dynamically, instead of triggering the last action, just show the menu. # The default action of a menu changes dynamically, instead of triggering the last action, just show the menu.
action = { 'handler': 'toolAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'showMenu': bool(men) } action = { 'handler': 'tool', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'showMenu': bool(men) }
group.append({'icon':icon, 'text':text, 'toolTipHTML': tbt.toolTip(), 'action': json.dumps(action), 'subitems': subgroup}) group.append({'icon':icon, 'text':text, 'toolTip': tbt.toolTip(), 'action': json.dumps(action), 'subitems': subgroup})
# TODO: move the 'workbenches' field to the itemgroup # TODO: move the 'workbenches' field to the itemgroup
action = { 'handler': 'toolbarAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName } action = { 'handler': 'toolbar', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName }
itemGroups.append({ itemGroups.append({
'icon': QtGui.QIcon(':/icons/Group.svg'), 'icon': QtGui.QIcon(':/icons/Group.svg'),
'text': toolbarName, 'text': toolbarName,
'toolTipHTML': '<p>Display toolbar ' + toolbarName + '</p><p>This toolbar appears in the following workbenches: <ul>' + ''.join(['<li>' + wb + '</li>' for wb in toolbarIsInWorkbenches]) + '</ul></p>', 'toolTip': '',
'action': json.dumps(action), 'action': json.dumps(action),
'subitems': group 'subitems': group
}) })
@ -371,23 +570,23 @@ if True:
group = [] group = []
for o in doc.Objects: for o in doc.Objects:
#all_actions.append(lambda: ) #all_actions.append(lambda: )
action = { 'handler': 'documentObjectAction', 'document': o.Document.Name, 'object': o.Name } action = { 'handler': 'documentObject', 'document': o.Document.Name, 'object': o.Name }
item = { item = {
'icon': o.ViewObject.Icon if o.ViewObject and o.ViewObject.Icon else None, 'icon': o.ViewObject.Icon if o.ViewObject and o.ViewObject.Icon else None,
'text': o.Label + ' (' + o.Name + ')', 'text': o.Label + ' (' + o.Name + ')',
# TODO: preview of the object # TODO: preview of the object
'toolTipHTML': '<p>' + o.Label + '</p><p><code>App.getDocument(' + repr(o.Document.Name) + ').getObject(' + repr(o.Name) + ')</code></p><p>' + '</p><p><img src="data:image/png;base64,.............."></p>', 'toolTip': { 'label': o.Label, 'name': o.Name, 'docName': o.Document.Name},
'action': json.dumps(action), 'action': json.dumps(action),
'subitems': [] 'subitems': []
} }
group.append(item) group.append(item)
action = { 'handler': 'documentAction', 'document': doc.Name } action = { 'handler': 'document', 'document': doc.Name }
itemGroups.append({ itemGroups.append({
'icon': QtGui.QIcon(':/icons/Document.svg'), 'icon': QtGui.QIcon(':/icons/Document.svg'),
'text': doc.Label + ' (' + doc.Name + ')', 'text': doc.Label + ' (' + doc.Name + ')',
# TODO: preview of the document # TODO: preview of the document
'toolTipHTML': '<p>' + doc.Label + '</p><p><code>App.getDocument(' + repr(doc.Name) + ')</code></p><p><img src="data:image/png;base64,.............."></p>', 'toolTip': { 'label': doc.Label, 'name': doc.Name},
'action':json.dumps(action), 'action':json.dumps(action),
'subitems': group }) 'subitems': group })
if App.ActiveDocument: if App.ActiveDocument:
@ -397,35 +596,74 @@ if True:
document(doc) document(doc)
return itemGroups return itemGroups
def serializeItemGroup(itemGroup): def serializeItemGroup(itemGroup):
return { return {
'icon': serializeIcon(itemGroup['icon']), 'icon': serializeIcon(itemGroup['icon']),
'text': itemGroup['text'], 'text': itemGroup['text'],
'toolTipHTML': itemGroup['toolTipHTML'], 'toolTip': itemGroup['toolTip'],
'action': itemGroup['action'], 'action': itemGroup['action'],
'subitems': serializeItemGroups(itemGroup['subitems']) 'subitems': serializeItemGroups(itemGroup['subitems'])
} }
def serializeItemGroups(itemGroups): def serializeItemGroups(itemGroups):
return [serializeItemGroup(itemGroup) for itemGroup in itemGroups] return [serializeItemGroup(itemGroup) for itemGroup in itemGroups]
def serialize(itemGroups): def serialize(itemGroups):
return json.dumps(serializeItemGroups(itemGroups)) return json.dumps(serializeItemGroups(itemGroups))
def deserializeItemGroup(itemGroup): def deserializeItemGroup(itemGroup):
return { return {
'icon': deserializeIcon(itemGroup['icon']), 'icon': deserializeIcon(itemGroup['icon']),
'text': itemGroup['text'], 'text': itemGroup['text'],
'toolTipHTML': itemGroup['toolTipHTML'], 'toolTip': itemGroup['toolTip'],
'action': itemGroup['action'], 'action': itemGroup['action'],
'subitems': deserializeItemGroups(itemGroup['subitems']) 'subitems': deserializeItemGroups(itemGroup['subitems'])
} }
def deserializeItemGroups(serializedItemGroups): def deserializeItemGroups(serializedItemGroups):
return [deserializeItemGroup(itemGroup) for itemGroup in serializedItemGroups] return [deserializeItemGroup(itemGroup) for itemGroup in serializedItemGroups]
def deserialize(serializeItemGroups): def deserialize(serializeItemGroups):
return deserializeItemGroups(json.loads(serializedItemGroups)) return deserializeItemGroups(json.loads(serializedItemGroups))
itemGroups = None
serializedItemGroups = None
def refreshToolbars(loadAllWorkbenches = True):
global itemGroups, serializedItemGroups
if loadAllWorkbenches:
activeWorkbench = FreeCADGui.activeWorkbench().name()
lbl = QtGui.QLabel('Loading workbench … (…/…)')
lbl.show()
lst = FreeCADGui.listWorkbenches()
for i, wb in enumerate(lst):
msg = 'Loading workbench ' + wb + ' (' + str(i) + '/' + str(len(lst)) + ')'
print(msg)
lbl.setText(msg)
geo = lbl.geometry()
geo.setSize(lbl.sizeHint())
lbl.setGeometry(geo)
lbl.repaint()
try:
FreeCADGui.activateWorkbench(wb)
except:
pass
lbl.hide()
FreeCADGui.activateWorkbench(activeWorkbench)
serializedItemGroups = serialize(GatherTools()) serializedItemGroups = serialize(GatherTools())
# TODO: save serialized tools in App.getUserAppDataDir() ################################################################################################################
# + never cache the document objects
itemGroups = deserialize(serializedItemGroups) itemGroups = deserialize(serializedItemGroups)
# # Avoid garbage collection by storing the action in a global variable
wax = None
def addToolSearchBox():
global wax, itemGroups, serializedItemGroups
if itemGroups is None:
if serializedItemGroups is None:
refreshToolbars(False)
else:
itemGroups = deserialize(serializedItemGroups)
mw = FreeCADGui.getMainWindow()
mbr = mw.findChildren(QtGui.QToolBar, 'File')[0]
# Create search box widget
sea = SearchBox(itemGroups) sea = SearchBox(itemGroups)
def onResultSelected(index, metadata): def onResultSelected(index, metadata):
action = json.loads(metadata) action = json.loads(metadata)
@ -434,4 +672,7 @@ if True:
wax = QtGui.QWidgetAction(None) wax = QtGui.QWidgetAction(None)
wax.setDefaultWidget(sea) wax.setDefaultWidget(sea)
#mbr.addWidget(sea) #mbr.addWidget(sea)
print("addAction" + repr(mbr) + ' add(' + repr(wax))
mbr.addAction(wax) mbr.addAction(wax)
addToolSearchBox()

View File

@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:export-ydpi="90.000000" inkscape:export-xdpi="90.000000" inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png" width="48px" height="48px" id="svg11300" sodipodi:version="0.32" inkscape:version="0.48.0 r9654" sodipodi:docname="Tools-spanner-hammer.svg" version="1.1">
<defs id="defs3">
<linearGradient id="linearGradient3805">
<stop style="stop-color:#c27e13;stop-opacity:1;" offset="0" id="stop3807"/>
<stop id="stop3813" offset="0.26878971" style="stop-color:#c27e13;stop-opacity:0.49803922;"/>
<stop style="stop-color:#c27e13;stop-opacity:0.9741379;" offset="1" id="stop3809"/>
</linearGradient>
<linearGradient id="linearGradient3742">
<stop style="stop-color:#666864;stop-opacity:1;" offset="0" id="stop3744"/>
<stop id="stop3750" offset="0.5" style="stop-color:#494a47;stop-opacity:0.49803922;"/>
<stop style="stop-color:#666864;stop-opacity:1;" offset="1" id="stop3746"/>
</linearGradient>
<linearGradient inkscape:collect="always" id="linearGradient2257">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop2259"/>
<stop style="stop-color:#ffffff;stop-opacity:0;" offset="1" id="stop2261"/>
</linearGradient>
<linearGradient id="linearGradient3087">
<stop style="stop-color:#3465a4;stop-opacity:1;" offset="0" id="stop3089"/>
<stop id="stop3095" offset="0" style="stop-color:#9fbce1;stop-opacity:1;"/>
<stop style="stop-color:#6b95ca;stop-opacity:1;" offset="0" id="stop2242"/>
<stop id="stop2244" offset="0.75" style="stop-color:#3d6aa5;stop-opacity:1;"/>
<stop style="stop-color:#386eb4;stop-opacity:1;" offset="1" id="stop3091"/>
</linearGradient>
<linearGradient id="linearGradient3077">
<stop style="stop-color:#98a0a9;stop-opacity:1;" offset="0" id="stop3079"/>
<stop style="stop-color:#c3d0dd;stop-opacity:1;" offset="1" id="stop3081"/>
</linearGradient>
<linearGradient id="linearGradient3061">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop3063"/>
<stop style="stop-color:#000000;stop-opacity:1;" offset="1" id="stop3065"/>
</linearGradient>
<linearGradient id="linearGradient3049">
<stop style="stop-color:#b6b6b6;stop-opacity:1;" offset="0" id="stop3051"/>
<stop id="stop2262" offset="0.5" style="stop-color:#f2f2f2;stop-opacity:1;"/>
<stop style="stop-color:#fafafa;stop-opacity:1;" offset="0.67612958" id="stop2264"/>
<stop id="stop2268" offset="0.84051722" style="stop-color:#d8d8d8;stop-opacity:1;"/>
<stop id="stop2266" offset="0.875" style="stop-color:#f2f2f2;stop-opacity:1;"/>
<stop style="stop-color:#dbdbdb;stop-opacity:1;" offset="1" id="stop3053"/>
</linearGradient>
<linearGradient inkscape:collect="always" id="linearGradient3041">
<stop style="stop-color:#000000;stop-opacity:1;" offset="0" id="stop3043"/>
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" id="stop3045"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3049" id="linearGradient3055" x1="19.648342" y1="42.253601" x2="20.631224" y2="6.7758031" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87827,0,0,0.87827,-0.9434039,3.282804)"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3061" id="linearGradient3067" x1="50.152931" y1="-3.6324477" x2="25.291086" y2="-4.3002653" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87826988,0,0,0.87826988,1.6925621,2.9668149)"/>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3041" id="radialGradient2260" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,0.374558,0,24.47041)" cx="24.8125" cy="39.125" fx="24.8125" fy="39.125" r="17.6875"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient2257" id="linearGradient3139" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.007254,-0.02636526,0.02636526,1.007254,1.0126591,-1.2060461)" x1="12.004697" y1="35.688461" x2="10.650805" y2="33.194965"/>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3041" id="radialGradient3145" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,0.374558,0,24.47041)" cx="24.8125" cy="39.125" fx="24.8125" fy="39.125" r="17.6875"/>
<filter y="0" x="0" filterUnits="userSpaceOnUse" id="ShadowBlur">
<feGaussianBlur id="feGaussianBlur6" stdDeviation="2" in="SourceGraphic"/>
</filter>
<clipPath id="clipoutline2" clipPathUnits="userSpaceOnUse">
<path d="M 55.8,12 L 56.56,12.16 C 65.4,9.22 85.24,20.8 80.82,31.12 C 79.7,32.74 79.89,35.62 81.24,37.43 L 56.43,62.25 C 55.04,60.86 51.95,60.89 50.53,62.25 L 9.5,101.63 C 5.76,105.22 8.7,108.27 10.28,109.88 L 17.64,117.24 C 19.25,118.84 23.5,121 26.52,117.8 L 65.24,76.99 C 66.59,75.57 66.6,72.52 65.24,71.12 C 65.24,71.12 65.25,71.1 65.24,71.1 L 90.15,46.2 C 92.78,48.57 95.263827,48.774854 97.62,48.13 C 102.17057,46.884568 104.70725,51.336643 107.43,54.59 L 106.2,55.8 C 104.38,57.64 104.55,60.74 106.59,62.78 C 108.63,64.82 111.74,64.99 113.56,63.17 L 121.7,55.04 C 123.52,53.2 123.35,50.1 121.3,48.07 C 119.46,46.22 116.79,45.98 114.93,47.3 C 109.35,41.04 112.28,40.1 104.92,31.95 L 91.13,17.86 C 81.2,8.12 68.3,8.13 55.8,12 z" id="outline2"/>
</clipPath>
<clipPath id="clipoutline1" clipPathUnits="userSpaceOnUse">
<path d="M 22.42,17.2 L 35.38,30.15 L 32.88,38.93 L 24.1,41.43 L 11.14,28.47 L 5.28,34.33 L 20.17,55.79 L 37.73,52.45 L 99.36,118.07 C 108.8,127.58 121.53,113.32 112.7,105.2 L 46.85,42.92 L 50.86,25.16 L 28.72,10.89 L 22.42,17.2 z M 103.48,109.17 C 105.16,107.49 107.88,107.49 109.56,109.17 C 111.24,110.85 111.24,113.57 109.56,115.25 C 107.88,116.92 105.16,116.92 103.48,115.25 C 101.8,113.57 101.8,110.85 103.48,109.17 z" id="outline1"/>
</clipPath>
<linearGradient gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.30267,-3.20138)" y2="86.68" x2="84.3" y1="66.2" x1="73.88" id="lgdg" xlink:href="#BlackTransparent"/>
<linearGradient gradientTransform="matrix(0.26,0,0,0.26,-106.57,101.57)" gradientUnits="userSpaceOnUse" y2="-241.2" x2="655.42" y1="-137.5" x1="550.96" id="lg55" xlink:href="#WhiteTransparent"/>
<linearGradient gradientTransform="matrix(0.2,0,0,0.2,-80.94,88.3)" gradientUnits="userSpaceOnUse" y2="-186.26" x2="607.66" y1="-252.6" x1="646.24" id="lg51" xlink:href="#WhiteTransparent"/>
<linearGradient y2="185.8" x2="71.77" y1="189.14" x1="75.1" id="lg19" xlink:href="#BlackTransparent" gradientTransform="matrix(1.05,0,0,1.05,0.13,-140.54)"/>
<linearGradient spreadMethod="pad" gradientTransform="matrix(7.75e-2,7.75e-2,-0.36,0.36,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="124" x2="626.1" y1="124" x1="577.03" id="lg16" xlink:href="#WhiteTransparent"/>
<linearGradient gradientTransform="matrix(0.3,0.3,-9e-2,9e-2,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="368.27" x2="146.15" y1="288.57" x1="146.15" id="lg12" xlink:href="#WhiteTransparent"/>
<linearGradient gradientTransform="matrix(0.33,0.33,-9.44e-2,9.44e-2,9.03,-48.1)" gradientUnits="userSpaceOnUse" y2="-1.72" x2="258.96" y1="62.74" x1="258.96" id="lg09" xlink:href="#WhiteTransparent"/>
<linearGradient spreadMethod="pad" gradientTransform="matrix(9.98e-2,9.98e-2,-0.28,0.28,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="286.56" x2="419.88" y1="286.56" x1="543.1" id="lg05" xlink:href="#BlackTransparent"/>
<linearGradient gradientTransform="matrix(8.07e-2,7.75e-2,-0.35,0.36,77.14,-45.89)" gradientUnits="userSpaceOnUse" y2="254.83" x2="615.59" y1="254.36" x1="512.7" id="lg02" xlink:href="#WhiteTransparent"/>
<linearGradient gradientTransform="matrix(0.1,0.1,-0.22,0.2,75.6,-33.53)" gradientUnits="userSpaceOnUse" y2="118.3" x2="638.3" y1="69.99" x1="637.03" id="lg1999" xlink:href="#WhiteTransparent"/>
<linearGradient id="BlackTransparent">
<stop id="stop3272" offset="0" style="stop-color:black;stop-opacity:1"/>
<stop id="stop3274" offset="1" style="stop-color:black;stop-opacity:0"/>
</linearGradient>
<linearGradient id="WhiteTransparent">
<stop id="stop3267" offset="0" style="stop-color:white;stop-opacity:1"/>
<stop id="stop3269" offset="1" style="stop-color:white;stop-opacity:0"/>
</linearGradient>
<filter y="0" x="0" filterUnits="userSpaceOnUse" id="ShadowBlur-3">
<feGaussianBlur id="feGaussianBlur6-4" stdDeviation="2" in="SourceGraphic"/>
</filter>
<clipPath id="clipoutline2-5" clipPathUnits="userSpaceOnUse">
<path d="M 55.8,12 L 56.56,12.16 C 65.4,9.22 85.24,20.8 80.82,31.12 C 79.7,32.74 79.89,35.62 81.24,37.43 L 56.43,62.25 C 55.04,60.86 51.95,60.89 50.53,62.25 L 9.5,101.63 C 5.76,105.22 8.7,108.27 10.28,109.88 L 17.64,117.24 C 19.25,118.84 23.5,121 26.52,117.8 L 65.24,76.99 C 66.59,75.57 66.6,72.52 65.24,71.12 C 65.24,71.12 65.25,71.1 65.24,71.1 L 90.15,46.2 C 92.78,48.57 95.263827,48.774854 97.62,48.13 C 102.17057,46.884568 104.70725,51.336643 107.43,54.59 L 106.2,55.8 C 104.38,57.64 104.55,60.74 106.59,62.78 C 108.63,64.82 111.74,64.99 113.56,63.17 L 121.7,55.04 C 123.52,53.2 123.35,50.1 121.3,48.07 C 119.46,46.22 116.79,45.98 114.93,47.3 C 109.35,41.04 112.28,40.1 104.92,31.95 L 91.13,17.86 C 81.2,8.12 68.3,8.13 55.8,12 z" id="outline2-9"/>
</clipPath>
<clipPath id="clipoutline1-1" clipPathUnits="userSpaceOnUse">
<path d="M 22.42,17.2 L 35.38,30.15 L 32.88,38.93 L 24.1,41.43 L 11.14,28.47 L 5.28,34.33 L 20.17,55.79 L 37.73,52.45 L 99.36,118.07 C 108.8,127.58 121.53,113.32 112.7,105.2 L 46.85,42.92 L 50.86,25.16 L 28.72,10.89 L 22.42,17.2 z M 103.48,109.17 C 105.16,107.49 107.88,107.49 109.56,109.17 C 111.24,110.85 111.24,113.57 109.56,115.25 C 107.88,116.92 105.16,116.92 103.48,115.25 C 101.8,113.57 101.8,110.85 103.48,109.17 z" id="outline1-3"/>
</clipPath>
<linearGradient gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.30267,-3.20138)" y2="86.68" x2="84.3" y1="66.2" x1="73.88" id="lgdg-1" xlink:href="#BlackTransparent-0"/>
<linearGradient gradientTransform="matrix(0.26,0,0,0.26,-106.57,101.57)" gradientUnits="userSpaceOnUse" y2="-241.2" x2="655.42" y1="-137.5" x1="550.96" id="lg55-4" xlink:href="#WhiteTransparent-1"/>
<linearGradient gradientTransform="matrix(0.2,0,0,0.2,-80.94,88.3)" gradientUnits="userSpaceOnUse" y2="-186.26" x2="607.66" y1="-252.6" x1="646.24" id="lg51-0" xlink:href="#WhiteTransparent-1"/>
<linearGradient y2="6037.5261" x2="2375.4978" y1="6145.3078" x1="2482.957" id="lg19-8" xlink:href="#BlackTransparent-0" gradientUnits="userSpaceOnUse"/>
<linearGradient spreadMethod="pad" gradientTransform="matrix(0.0775,0.0775,-0.36,0.36,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="124" x2="626.1" y1="124" x1="577.03" id="lg16-0" xlink:href="#WhiteTransparent-1"/>
<linearGradient gradientTransform="matrix(0.3,0.3,-0.09,0.09,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="368.27" x2="146.15" y1="288.57" x1="146.15" id="lg12-5" xlink:href="#WhiteTransparent-1"/>
<linearGradient gradientTransform="matrix(0.33,0.33,-0.0944,0.0944,9.03,-48.1)" gradientUnits="userSpaceOnUse" y2="-1.72" x2="258.96" y1="62.74" x1="258.96" id="lg09-3" xlink:href="#WhiteTransparent-1"/>
<linearGradient spreadMethod="pad" gradientTransform="matrix(0.0998,0.0998,-0.28,0.28,78.84,-44.8)" gradientUnits="userSpaceOnUse" y2="286.56" x2="419.88" y1="286.56" x1="543.1" id="lg05-6" xlink:href="#BlackTransparent-0"/>
<linearGradient gradientTransform="matrix(0.0807,0.0775,-0.35,0.36,77.14,-45.89)" gradientUnits="userSpaceOnUse" y2="254.83" x2="615.59" y1="254.36" x1="512.7" id="lg02-7" xlink:href="#WhiteTransparent-1"/>
<linearGradient gradientTransform="matrix(0.1,0.1,-0.22,0.2,75.6,-33.53)" gradientUnits="userSpaceOnUse" y2="118.3" x2="638.3" y1="69.99" x1="637.03" id="lg1999-8" xlink:href="#WhiteTransparent-1"/>
<linearGradient id="BlackTransparent-0">
<stop id="stop3550" offset="0" style="stop-color:black;stop-opacity:1"/>
<stop id="stop3552" offset="1" style="stop-color:black;stop-opacity:0"/>
</linearGradient>
<linearGradient id="WhiteTransparent-1">
<stop id="stop3545" offset="0" style="stop-color:white;stop-opacity:1"/>
<stop id="stop3547" offset="1" style="stop-color:white;stop-opacity:0"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3742" id="linearGradient3748" x1="-32.163666" y1="11.982862" x2="-34.732647" y2="14.757363" gradientUnits="userSpaceOnUse" gradientTransform="translate(69.519608,-1.684877)"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3077" id="linearGradient3758" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)" x1="29.555725" y1="17.722818" x2="30.433241" y2="18.629581"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3077" id="linearGradient3760" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)" x1="38.227654" y1="13.602527" x2="37.53537" y2="6.6285896"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3805" id="linearGradient3811" x1="15.909903" y1="29.261671" x2="18.384775" y2="31.736544" gradientUnits="userSpaceOnUse"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3742" id="linearGradient3820" gradientUnits="userSpaceOnUse" gradientTransform="translate(69.519608,-1.684877)" x1="-32.163666" y1="11.982862" x2="-34.732647" y2="14.757363"/>
<linearGradient gradientTransform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,118.87476,36.669709)" inkscape:collect="always" xlink:href="#linearGradient3805-8" id="linearGradient3811-9" x1="15.909903" y1="29.261671" x2="18.384775" y2="31.736544" gradientUnits="userSpaceOnUse"/>
<linearGradient id="linearGradient3805-8">
<stop style="stop-color:#c27e13;stop-opacity:1;" offset="0" id="stop3807-3"/>
<stop id="stop3813-6" offset="0.26878971" style="stop-color:#c27e13;stop-opacity:0.49803922;"/>
<stop style="stop-color:#c27e13;stop-opacity:0.9741379;" offset="1" id="stop3809-4"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3077-1" id="linearGradient3758-7" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87827,0,0,0.87827,150.91626,23.296793)" x1="29.555725" y1="17.722818" x2="30.433241" y2="18.629581"/>
<linearGradient id="linearGradient3077-1">
<stop style="stop-color:#98a0a9;stop-opacity:1;" offset="0" id="stop3079-5"/>
<stop style="stop-color:#c3d0dd;stop-opacity:1;" offset="1" id="stop3081-9"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3061-4" id="linearGradient3067-4" x1="50.152931" y1="-3.6324477" x2="25.291086" y2="-4.3002653" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87826988,0,0,0.87826988,120.30601,-90.957017)"/>
<linearGradient id="linearGradient3061-4">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop3063-2"/>
<stop style="stop-color:#000000;stop-opacity:1;" offset="1" id="stop3065-0"/>
</linearGradient>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3041-0" id="radialGradient3145-0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,0.374558,0,24.47041)" cx="24.8125" cy="39.125" fx="24.8125" fy="39.125" r="17.6875"/>
<linearGradient inkscape:collect="always" id="linearGradient3041-0">
<stop style="stop-color:#000000;stop-opacity:1;" offset="0" id="stop3043-0"/>
<stop style="stop-color:#000000;stop-opacity:0;" offset="1" id="stop3045-8"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3077-1" id="linearGradient3760-1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)" x1="38.227654" y1="13.602527" x2="37.53537" y2="6.6285896"/>
<linearGradient id="linearGradient3850">
<stop style="stop-color:#98a0a9;stop-opacity:1;" offset="0" id="stop3852"/>
<stop style="stop-color:#c3d0dd;stop-opacity:1;" offset="1" id="stop3854"/>
</linearGradient>
<linearGradient y2="6.6285896" x2="37.53537" y1="13.602527" x1="38.227654" gradientTransform="matrix(0.87827,0,0,0.87827,150.91626,23.296793)" gradientUnits="userSpaceOnUse" id="linearGradient3863" xlink:href="#linearGradient3077-1" inkscape:collect="always"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3805-8" id="linearGradient4719" x1="166.95419" y1="48.512695" x2="171.73305" y2="48.512695" gradientUnits="userSpaceOnUse"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3805-8" id="linearGradient4725" gradientUnits="userSpaceOnUse" x1="166.95419" y1="48.512695" x2="171.73305" y2="48.512695"/>
</defs>
<sodipodi:namedview stroke="#204a87" fill="#3465a4" id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="0.25490196" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="4" inkscape:cx="13.9707" inkscape:cy="17.102584" inkscape:current-layer="layer1" showgrid="false" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:showpageshadow="false" inkscape:window-width="1340" inkscape:window-height="878" inkscape:window-x="100" inkscape:window-y="0" inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" inkscape:object-nodes="true">
<inkscape:grid type="xygrid" id="grid3762"/>
<sodipodi:guide orientation="1,0" position="169.35207,15.556349" id="guide3928"/>
</sodipodi:namedview>
<metadata id="metadata4">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>http://jimmac.musichall.cz</dc:source>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/"/>
<dc:title/>
<dc:subject>
<rdf:Bag>
<rdf:li>preferences</rdf:li>
<rdf:li>settings</rdf:li>
<rdf:li>control panel</rdf:li>
<rdf:li>tweaks</rdf:li>
<rdf:li>system</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
<cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
<cc:requires rdf:resource="http://web.resource.org/cc/Notice"/>
<cc:requires rdf:resource="http://web.resource.org/cc/Attribution"/>
<cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
<cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer">
<path transform="matrix(0.751118,0,0,0.578703,13.560478,17.678533)" d="m 42.5,39.125 a 17.6875,6.625 0 1 1 -35.375,0 17.6875,6.625 0 1 1 35.375,0 z" sodipodi:ry="6.625" sodipodi:rx="17.6875" sodipodi:cy="39.125" sodipodi:cx="24.8125" id="path2258" style="opacity:0.19886367;color:#000000;fill:url(#radialGradient2260);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" sodipodi:type="arc"/>
<path sodipodi:type="arc" style="opacity:0.3125;color:#000000;fill:url(#radialGradient3145);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" id="path3039" sodipodi:cx="24.8125" sodipodi:cy="39.125" sodipodi:rx="17.6875" sodipodi:ry="6.625" d="m 42.5,39.125 a 17.6875,6.625 0 1 1 -35.375,0 17.6875,6.625 0 1 1 35.375,0 z" transform="matrix(0.836071,0,0,0.685436,-7.9399989,15.032933)"/>
<path style="color:#000000;fill:url(#linearGradient3055);fill-opacity:1;fill-rule:nonzero;stroke:#888a85;stroke-width:0.9999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" d="M 14.426321,19.530799 33.41891,38.962523 c 0.768486,0.87827 3.20351,1.557061 4.830485,0 1.571126,-1.503613 1.207621,-3.622864 -0.329351,-5.159837 L 19.695941,14.261179 c 2.25,-6.25 -2.303889,-11.499568 -8.178889,-10.374568 l -1.262514,1.1527294 3.952215,3.7326476 0.219568,3.293516 -2.950842,2.693603 L 7.94908,14.370963 4.3262171,10.967667 c 0,0 -1.270122,1.254904 -1.270122,1.254904 -0.590763,5.641317 5.3077259,10.683228 11.3702259,7.308228 z" id="path2140" sodipodi:nodetypes="cczcccccccccsc" inkscape:connector-curvature="0"/>
<path sodipodi:nodetypes="cczccccccccccc" id="path3057" d="m 14.636993,18.255223 19.202882,20.027612 c 0.594907,0.679893 2.479927,1.205365 3.739414,0 1.216253,-1.163989 0.934853,-2.80456 -0.25496,-3.994374 L 18.832797,14.667306 c 1.5,-6.5 -1.858788,-10.0046375 -6.858788,-9.8796375 l -0.270131,0.2733768 3.602923,3.2362667 0.130166,4.181794 -3.613917,3.298483 -4.242306,-0.458209 -3.1765819,-2.991481 -0.352638,0.430059 c -0.3125,5.96875 6.4917189,8.684765 10.5854689,5.497265 z" style="opacity:0.42613639;color:#000000;fill:none;stroke:#ffffff;stroke-width:0.99999917;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" inkscape:connector-curvature="0"/>
<rect style="opacity:0.17045456;color:#000000;fill:none;stroke:url(#linearGradient3067);stroke-width:0.99999708;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" id="rect3059" width="23.268272" height="2.055491" x="24.549597" y="-1.3018785" rx="0.88388062" ry="0.88388062" transform="matrix(0.69793809,0.7161581,-0.7161581,0.69793809,0,0)"/>
<path sodipodi:type="arc" style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#a1a1a1;stroke-width:1.13860166;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" id="path2146" sodipodi:cx="41.875" sodipodi:cy="37.5" sodipodi:rx="1.375" sodipodi:ry="1.375" d="m 43.25,37.5 a 1.375,1.375 0 1 1 -2.75,0 1.375,1.375 0 1 1 2.75,0 z" transform="matrix(0.87827,0,0,0.87827,-1.0531879,3.392587)"/>
<g id="g4721" transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,-66.505551,-125.50913)">
<path sodipodi:nodetypes="ccscccscccc" inkscape:connector-curvature="0" d="m 171.04228,65.702992 c 2.28219,-0.05133 2.24901,-2.610868 2.18061,-5.344146 -0.11958,-4.781402 -0.82929,-4.219524 -0.97878,-10.196283 -0.14948,-5.976752 0.39385,-17.60825 0.39385,-17.60825 -0.0476,-1.904867 0.28281,-4.437364 -3.28589,-4.203494 -3.5687,-0.23387 -3.23825,2.298627 -3.28589,4.203494 0,0 0.54333,11.631498 0.39385,17.60825 -0.14949,5.976759 -0.8592,5.414881 -0.97878,10.196283 -0.0684,2.733278 -0.10158,5.292819 2.18061,5.344146 0,0 -0.16595,0.01887 1.69021,0.01887 1.85616,0 1.69021,-0.01887 1.69021,-0.01887 z" style="color:#000000;fill:#e9b96e;fill-opacity:1;fill-rule:nonzero;stroke:#8f5902;stroke-width:0.9999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" id="path3930"/>
<path sodipodi:nodetypes="cccsscccsccccscssccccccc" inkscape:connector-curvature="0" id="path3933" d="m 169.25,29.84375 0.0937,0 0.0937,0 c 0.71741,-0.04701 1.09477,0.07913 1.25,0.15625 0.15523,0.07712 0.17681,0.08319 0.25,0.25 0.13862,0.315936 0.16244,1.231053 0.1875,2.21875 0,0 5e-5,0.123852 0,0.125 -0.008,0.161592 -0.52689,11.520677 -0.375,17.59375 0.0763,3.048871 0.31707,4.559136 0.53125,5.78125 0.21418,1.222114 0.3796,2.122413 0.4375,4.4375 0.0341,1.360997 0.0171,2.612163 -0.15625,3.25 -0.0866,0.318919 -0.18098,0.434184 -0.21875,0.46875 l -3.96885,0 c -0.0378,-0.03457 -0.16335,-0.149831 -0.25,-0.46875 -0.1733,-0.637837 -0.19031,-1.889003 -0.15625,-3.25 0.0579,-2.315087 0.25457,-3.215386 0.46875,-4.4375 0.21418,-1.222114 0.45499,-2.732379 0.53125,-5.78125 0.15189,-6.073076 -0.39864,-17.432171 -0.40625,-17.59375 3.5e-4,-0.01399 -3.5e-4,-0.0173 0,-0.03125 -3e-5,-6.46e-4 0,-0.09375 0,-0.09375 0.0251,-0.987718 0.0489,-1.902811 0.1875,-2.21875 0.0732,-0.166809 0.0948,-0.172884 0.25,-0.25 0.15523,-0.07712 0.53259,-0.203264 1.25,-0.15625 z" style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:url(#linearGradient4725);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"/>
</g>
<g id="g3815" transform="matrix(0.99994307,0.01067021,-0.01067021,0.99994307,-0.7495296,-0.03414607)">
<path id="path3732" d="m 21.733036,4.2895778 0.277332,0.042544 c 3.129991,-1.2369372 10.517617,2.5440684 9.129271,6.3548182 -0.371659,0.606706 -0.245638,1.641758 0.277448,2.267696 l 3.388978,2.985578 c 0.995974,0.802387 1.896004,0.826701 2.733027,0.547066 1.616576,-0.540072 2.620446,1.015203 3.667507,2.134373 l -0.419521,0.46101 c -0.619764,0.700034 -0.496567,1.814841 0.280001,2.509971 0.776567,0.695128 1.901769,0.694372 2.521932,0.0016 l 2.7739,-3.095049 c 0.619765,-0.700034 0.496566,-1.814842 -0.283408,-2.506164 -0.700634,-0.630586 -1.668519,-0.663861 -2.313092,-0.150598 -2.137713,-2.146665 -1.099596,-2.544216 -3.917097,-5.337092 L 34.593883,5.6981833 C 30.817619,2.3830766 26.164662,2.6441733 21.733036,4.2896256 z" inkscape:connector-curvature="0" style="fill:url(#linearGradient3820);fill-opacity:1;stroke:none" sodipodi:nodetypes="cccccsccsccccccc"/>
<path inkscape:connector-curvature="0" id="path3723" d="m 27.582108,3.096373 c -1.931616,0.00639 -3.904914,0.4676146 -5.84375,1.1875 l 0.28125,0.0625 c 3.129991,-1.2369372 10.513346,2.5329998 9.125,6.34375 -0.371659,0.606706 -0.241836,1.624062 0.28125,2.25 l 3.375,3 c 0.995974,0.802387 1.912977,0.842135 2.75,0.5625 1.616576,-0.540072 2.609189,1.00583 3.65625,2.125 l -0.40625,0.46875 c -0.619764,0.700034 -0.495318,1.80487 0.28125,2.5 0.776567,0.695128 1.879837,0.692772 2.5,0 l 2.78125,-3.09375 c 0.619765,-0.700034 0.498724,-1.808678 -0.28125,-2.5 -0.700634,-0.630586 -1.667927,-0.669513 -2.3125,-0.15625 -2.137713,-2.146665 -1.088749,-2.550874 -3.90625,-5.34375 l -5.28125,-4.8125 c -2.124148,-1.8647475 -4.516494,-2.6019669 -7,-2.59375 z m 2.3125,1.875 c 1.286595,0.3229951 2.535902,0.8383499 3.6875,1.84375 0.0072,0.00628 0.02406,-0.00631 0.03125,0 l 5.21875,4.8125 c 1.245645,1.246066 1.544657,1.81074 1.875,2.46875 0.337666,0.672597 0.812808,1.620337 2,2.8125 l 0.9375,0.9375 1.0625,-0.84375 c 0.04744,-0.03777 0.165431,-0.09487 0.375,0.09375 l 0,0.03125 c 0.239625,0.212389 0.186689,0.340618 0.15625,0.375 l -2.78125,3.09375 c -0.01914,0.02138 -0.135341,0.08953 -0.375,-0.125 -0.226174,-0.202455 -0.217574,-0.329532 -0.1875,-0.375 0.002,-0.0031 -0.0019,-0.02907 0,-0.03125 l 0.40625,-0.4375 0.9375,-1.03125 -0.9375,-1 c -0.450094,-0.481091 -0.979142,-1.210212 -1.78125,-1.84375 -0.802108,-0.633538 -2.161514,-1.124227 -3.46875,-0.6875 -0.484354,0.161814 -0.654656,0.213761 -1.25,-0.25 l -0.0625,-0.03125 -3.15625,-2.78125 c -0.0042,-0.005 0.0036,-0.02347 0,-0.03125 -0.05479,-0.118961 -0.05906,-0.536691 -0.125,-0.53125 l 0.03125,-0.09375 0.0625,-0.15625 c 0.520861,-1.429665 0.187889,-2.9158091 -0.53125,-4.0625 -0.535535,-0.8539272 -1.303683,-1.5449522 -2.125,-2.15625 z" style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"/>
<path sodipodi:nodetypes="cccccsccsccccccc" style="fill:none;stroke:#555753" inkscape:connector-curvature="0" d="m 22.025169,3.8662307 0.275714,0.052011 c 3.170491,-1.1290894 10.424385,2.9025409 8.90643,6.6635423 -0.392205,0.59363 -0.301683,1.632389 0.199674,2.275863 l 3.284812,3.099816 c 0.967929,0.836004 1.8666,0.891107 2.712703,0.640283 1.634113,-0.484429 2.584166,1.104292 3.59231,2.258642 l -0.435053,0.446382 c -0.64336,0.678412 -0.558389,1.796783 0.193934,2.518083 0.752321,0.721299 1.87689,0.759053 2.520399,0.08791 l 2.878203,-2.9983 c 0.64336,-0.678413 0.558387,-1.796784 -0.197469,-2.514395 -0.678642,-0.654196 -1.644821,-0.720577 -2.306583,-0.229675 -2.062992,-2.21857 -1.011877,-2.580359 -3.732142,-5.468027 L 34.830273,5.7141704 C 31.16968,2.2717641 26.510513,2.3734615 22.025168,3.8662785 z" id="use3311"/>
</g>
</g>
<style id="style3292" type="text/css">
/* Specular Highlighting */
.low-specularity {opacity:0.25;}
.specularity {opacity:0.5;}
.high-specularity {opacity:0.75;}
.full-specularity {opacity:1;}
/* Shading */
.low-shade {opacity:0.25;}
.shade {opacity:0.5;}
.high-shade {opacity:0.75;}
.full-shade {opacity:1;}
/* Tango palette fill/stroke */
.black {fill:#000;}
.aluminium1 {fill:#eeeeec;}
.aluminium2 {fill:#d3d7cf;}
.aluminium6 {fill:#2e3436;}
.chocolate3 {fill:#8f5902;}
.chocolate2 {fill:#c17d11;}
.aluminium4 {fill:#888a85;}
/* Shadows: Back-Shadows &amp; Base Shadows */
.base-shadow {opacity:0.4;}
.outline-big {stroke:black;stroke-width:8;opacity:0.25;stroke-linejoin:round;}
.outline-small {stroke:black;stroke-width:4;opacity:0.5;stroke-linejoin:round;}
.stroke-highlight {fill:none;stroke:white;stroke-opacity:0.2;stroke-width:4;stroke-linejoin:round;}
</style>
<style id="style3570" type="text/css">
/* Specular Highlighting */
.low-specularity {opacity:0.25;}
.specularity {opacity:0.5;}
.high-specularity {opacity:0.75;}
.full-specularity {opacity:1;}
/* Shading */
.low-shade {opacity:0.25;}
.shade {opacity:0.5;}
.high-shade {opacity:0.75;}
.full-shade {opacity:1;}
/* Tango palette fill/stroke */
.black {fill:#000;}
.aluminium1 {fill:#eeeeec;}
.aluminium2 {fill:#d3d7cf;}
.aluminium6 {fill:#2e3436;}
.chocolate3 {fill:#8f5902;}
.chocolate2 {fill:#c17d11;}
.aluminium4 {fill:#888a85;}
/* Shadows: Back-Shadows &amp; Base Shadows */
.base-shadow {opacity:0.4;}
.outline-big {stroke:black;stroke-width:8;opacity:0.25;stroke-linejoin:round;}
.outline-small {stroke:black;stroke-width:4;opacity:0.5;stroke-linejoin:round;}
.stroke-highlight {fill:none;stroke:white;stroke-opacity:0.2;stroke-width:4;stroke-linejoin:round;}
</style>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,2 @@
https://commons.wikimedia.org/wiki/File:Tools-spanner-hammer.svg
http://tango.freedesktop.org/Tango_Desktop_Project