331 lines
12 KiB
Python
331 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
# vim: sts=4 sw=4 et
|
|
# GladeVcp MDI history widget
|
|
#
|
|
# Copyright (c) 2011 Pavel Shramov <shramov@mexmat.net>
|
|
#
|
|
# This program 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 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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.
|
|
|
|
import os
|
|
|
|
import gi
|
|
gi.require_version("Gtk","3.0")
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gdk
|
|
from gi.repository import GObject
|
|
from gi.repository import Pango
|
|
|
|
if __name__ == "__main__":
|
|
from hal_widgets import _HalWidgetBase
|
|
from hal_actions import _EMC_ActionBase, ensure_mode
|
|
else:
|
|
from .hal_widgets import _HalWidgetBase
|
|
from .hal_actions import _EMC_ActionBase, ensure_mode
|
|
|
|
import linuxcnc
|
|
from hal_glib import GStat
|
|
from gladevcp.core import Info
|
|
|
|
GSTAT = GStat()
|
|
INFO = Info()
|
|
|
|
# Set up logging
|
|
from qtvcp import logger
|
|
LOG = logger.getLogger(__name__)
|
|
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL, VERBOSE
|
|
|
|
# path to TCL for external programs eg. halshow
|
|
try:
|
|
TCLPATH = os.environ['LINUXCNC_TCL_DIR']
|
|
except:
|
|
pass
|
|
|
|
import gettext # to extract the strings to be translated
|
|
|
|
class EMC_MDIHistory(Gtk.VBox, _EMC_ActionBase):
|
|
'''
|
|
EMC_MDIHistory will store each MDI command to a file on your hard drive
|
|
and display the grabbed commands in a treeview so they can be used again
|
|
without typing the complete command again
|
|
'''
|
|
|
|
__gtype_name__ = 'EMC_MDIHistory'
|
|
__gproperties__ = {
|
|
'font_size_tree' : (GObject.TYPE_INT, 'Font Size', 'The font size of the tree view text',
|
|
8, 96, 10, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'font_size_entry' : (GObject.TYPE_INT, 'Font Size', 'The font size of the entry text',
|
|
8, 96, 10, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'use_double_click' : (GObject.TYPE_BOOLEAN, 'Enable submit a command using a double click', 'A double click on an entry will submit the selected command directly\nIt is not recommended to use this on real machines',
|
|
False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
}
|
|
__gproperties = __gproperties__
|
|
|
|
__gsignals__ = {
|
|
'exit': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, ()),
|
|
}
|
|
|
|
def __init__(self, *a, **kw):
|
|
Gtk.VBox.__init__(self, *a, **kw)
|
|
|
|
self.use_double_click = False
|
|
self.filename = os.path.expanduser(INFO.MDI_HISTORY_PATH)
|
|
|
|
self.model = Gtk.ListStore(str)
|
|
|
|
self.tv = Gtk.TreeView()
|
|
self.default_font = self.tv.get_style().font_desc.to_string()
|
|
self.tv.modify_font(Pango.FontDescription(self.default_font))
|
|
self.tv.set_model(self.model)
|
|
self.cell = Gtk.CellRendererText()
|
|
|
|
self.col = Gtk.TreeViewColumn(_("Command"))
|
|
self.col.pack_start(self.cell, True)
|
|
self.col.add_attribute(self.cell, 'text', 0)
|
|
|
|
self.tv.append_column(self.col)
|
|
self.tv.set_search_column(0)
|
|
self.tv.set_reorderable(False)
|
|
self.tv.set_headers_visible(True)
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.NONE)
|
|
|
|
scroll = Gtk.ScrolledWindow()
|
|
scroll.add(self.tv)
|
|
scroll.props.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC
|
|
scroll.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
|
|
|
|
self.entry = Gtk.Entry()
|
|
self.entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "gtk-ok")
|
|
self.entry.modify_font(Pango.FontDescription(self.default_font))
|
|
|
|
self.entry.connect('activate', self.submit)
|
|
self.entry.connect('icon-press', self.submit)
|
|
self.tv.connect('cursor-changed', self.select)
|
|
self.tv.connect('key_press_event', self.on_key_press_event)
|
|
self.connect('key_press_event', self.on_key_press_event)
|
|
self.tv.connect('button_press_event', self.on_button_press_event)
|
|
|
|
self.pack_start(scroll, True, True, 0)
|
|
self.pack_start(self.entry, False, False, 0)
|
|
GSTAT.connect('state-off', lambda w: self.set_sensitive(False))
|
|
GSTAT.connect('state-estop', lambda w: self.set_sensitive(False))
|
|
GSTAT.connect('interp-idle', lambda w: self.set_sensitive(self.machine_on()))
|
|
GSTAT.connect('interp-run', lambda w: self.set_sensitive(not self.is_auto_mode()))
|
|
GSTAT.connect('all-homed', lambda w: self.set_sensitive(self.machine_on()))
|
|
# this time lambda with two parameters, as not all homed will send also the unhomed joints
|
|
GSTAT.connect('not-all-homed', lambda w,uj: self.set_sensitive(INFO.NO_HOME_REQUIRED) )
|
|
self.reload()
|
|
self.show_all()
|
|
|
|
def reload(self):
|
|
self.model.clear()
|
|
|
|
try:
|
|
fp = open(self.filename)
|
|
except:
|
|
return
|
|
lines = map(str.strip, fp.readlines())
|
|
fp.close()
|
|
|
|
lines = filter(bool, lines)
|
|
for l in lines:
|
|
self.model.append((l,))
|
|
#path = (len(list(lines))-1,)
|
|
path = 0 #TODO: breaks the functionality
|
|
self.tv.scroll_to_cell(path)
|
|
self.tv.set_cursor(path)
|
|
self.entry.set_text('')
|
|
|
|
def submit(self, *a):
|
|
cmd = self.entry.get_text()
|
|
if cmd == 'HALMETER' or cmd == 'halmeter':
|
|
self.load_halmeter()
|
|
return
|
|
elif cmd == 'STATUS' or cmd == 'status':
|
|
self.load_status()
|
|
return
|
|
elif cmd == 'HALSHOW' or cmd == 'halshow':
|
|
self.load_halshow()
|
|
return
|
|
if not cmd:
|
|
return
|
|
ensure_mode(GSTAT.stat, self.linuxcnc, linuxcnc.MODE_MDI)
|
|
|
|
self.linuxcnc.mdi(cmd)
|
|
self.entry.set_text('')
|
|
self.entry.grab_focus()
|
|
|
|
add_to_file = True
|
|
# we need to put this in a try, because if the hal mdi history file is
|
|
# empty, the model is empty and we will get an None iter!
|
|
try:
|
|
actual = self.tv.get_cursor()[0]
|
|
iter = self.model.get_iter(actual)
|
|
old_cmd = self.model.get_value(iter,0)
|
|
|
|
lastiter = self._get_iter_last(self.model)
|
|
len = int(self.model.get_string_from_iter(lastiter))
|
|
|
|
if actual[0] == len and old_cmd == cmd:
|
|
add_to_file = False
|
|
except:
|
|
pass
|
|
|
|
if add_to_file:
|
|
try:
|
|
fp = open(self.filename, 'a')
|
|
fp.write(cmd + "\n")
|
|
fp.close()
|
|
except:
|
|
pass
|
|
|
|
last = self.model.append((cmd,))
|
|
path = self.model.get_path(last)
|
|
self.tv.scroll_to_cell(path)
|
|
self.tv.set_cursor(path)
|
|
self.entry.set_text('')
|
|
self.entry.grab_focus()
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.NONE)
|
|
|
|
def select(self, w):
|
|
idx = w.get_cursor()[0]
|
|
if idx is None:
|
|
return True
|
|
self.entry.set_text(self.model[idx][0])
|
|
self.entry.grab_focus()
|
|
self.entry.set_position(-1)
|
|
|
|
def on_key_press_event(self,w,event):
|
|
# get the keyname
|
|
keyname = Gdk.keyval_name(event.keyval)
|
|
# print(keyname)
|
|
idx = self.tv.get_cursor()[0]
|
|
if idx is None:
|
|
return True
|
|
|
|
lastiter = self._get_iter_last(self.model)
|
|
len = int(self.model.get_string_from_iter(lastiter))
|
|
|
|
#if nothing is selected, we need to activate the last one on up key
|
|
selection = self.tv.get_selection()
|
|
_, selected = selection.get_selected()
|
|
|
|
|
|
if keyname == 'Up':
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
|
|
if not selected:
|
|
self.tv.set_cursor(len)
|
|
else:
|
|
if idx[0] > 0:
|
|
self.tv.set_cursor(idx[0] - 1)
|
|
else:
|
|
self.tv.set_cursor(idx[0])
|
|
return True
|
|
|
|
if keyname == 'Down':
|
|
if not selected:
|
|
return True
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
|
|
if idx[0] < len:
|
|
self.tv.set_cursor(idx[0] + 1)
|
|
else:
|
|
self.tv.set_cursor(idx[0])
|
|
self.entry.set_text('')
|
|
self.entry.grab_focus()
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.NONE)
|
|
return True
|
|
|
|
if keyname == 'Escape':
|
|
self.entry.set_text('')
|
|
self.entry.grab_focus()
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.NONE)
|
|
|
|
def on_button_press_event(self,w,event):
|
|
idx = w.get_cursor()[0]
|
|
if idx is None:
|
|
return True
|
|
self.tv.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
|
|
self.entry.set_text(self.model[idx][0])
|
|
self.entry.grab_focus()
|
|
self.entry.set_position(-1)
|
|
if event.type == Gdk.EventType._2BUTTON_PRESS:
|
|
print("Double Click", self.use_double_click)
|
|
if self.use_double_click:
|
|
self.submit()
|
|
|
|
def load_halmeter(self):
|
|
p = os.popen("halmeter &")
|
|
def load_status(self):
|
|
p = os.popen("linuxcnctop > /dev/null &","w")
|
|
def load_halshow(self):
|
|
try:
|
|
p = os.popen("tclsh %s/bin/halshow.tcl &" % (TCLPATH))
|
|
except:
|
|
self.entry.set_text(_("ERROR loading halshow"))
|
|
|
|
def _get_iter_last(self, model):
|
|
itr = model.get_iter_first()
|
|
last = None
|
|
while itr:
|
|
last = itr
|
|
itr = model.iter_next(itr)
|
|
return last
|
|
|
|
def _change_font_entry(self, value):
|
|
font = self.default_font.split()[0]
|
|
new_font = font +" " + str(value)
|
|
self.entry.modify_font(Pango.FontDescription(new_font))
|
|
|
|
def _change_font_tree(self, value):
|
|
font = self.default_font.split()[0]
|
|
new_font = font +" " + str(value)
|
|
self.tv.modify_font(Pango.FontDescription(new_font))
|
|
|
|
# Get property
|
|
def do_get_property(self, property):
|
|
name = property.name.replace('-', '_')
|
|
if name in self.__gproperties.keys():
|
|
return getattr(self, name)
|
|
else:
|
|
raise AttributeError('unknown property %s' % property.name)
|
|
|
|
# Set property
|
|
def do_set_property(self, property, value):
|
|
try:
|
|
name = property.name.replace('-', '_')
|
|
if name in self.__gproperties.keys():
|
|
setattr(self, name, value)
|
|
self.queue_draw()
|
|
if name == "font_size_tree":
|
|
self._change_font_tree(value)
|
|
if name == "font_size_entry":
|
|
self._change_font_entry(value)
|
|
if name == "use_double_click":
|
|
self.use_double_click = value
|
|
else:
|
|
raise AttributeError('unknown property %s' % property.name)
|
|
except:
|
|
pass
|
|
|
|
# for testing without glade editor or LinuxCNC not running:
|
|
def main():
|
|
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
|
|
mdi = EMC_MDIHistory()
|
|
mdi.set_property("font_size_tree", 12)
|
|
mdi.set_property("font_size_entry", 20)
|
|
mdi.set_property("use_double_click", True)
|
|
window.add(mdi)
|
|
window.connect("destroy", Gtk.main_quit)
|
|
window.set_size_request(250, 400)
|
|
window.show_all()
|
|
Gtk.main()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|