hal_mdihistory.py
1 #!/usr/bin/env python 2 # vim: sts=4 sw=4 et 3 # GladeVcp MDI history widget 4 # 5 # Copyright (c) 2011 Pavel Shramov <shramov@mexmat.net> 6 # 7 # This program is free software: you can redistribute it and/or modify 8 # it under the terms of the GNU General Public License as published by 9 # the Free Software Foundation, either version 2 of the License, or 10 # (at your option) any later version. 11 # 12 # This program is distributed in the hope that it will be useful, 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # GNU General Public License for more details. 16 17 import os 18 import pango 19 20 import gobject, gtk 21 22 from hal_widgets import _HalWidgetBase 23 import linuxcnc 24 from hal_glib import GStat 25 from hal_actions import _EMC_ActionBase, ensure_mode 26 # path to TCL for external programs eg. halshow 27 try: 28 TCLPATH = os.environ['LINUXCNC_TCL_DIR'] 29 except: 30 pass 31 32 class EMC_MDIHistory(gtk.VBox, _EMC_ActionBase): 33 ''' 34 EMC_MDIHistory will store each MDI command to a file on your hard drive 35 and display the grabbed commands in a treeview so they can be used again 36 without typing the complete comand again 37 ''' 38 39 __gtype_name__ = 'EMC_MDIHistory' 40 __gproperties__ = { 41 'font_size_tree' : (gobject.TYPE_INT, 'Font Size', 'The font size of the tree view text', 42 8, 96, 10, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 43 'font_size_entry' : (gobject.TYPE_INT, 'Font Size', 'The font size of the entry text', 44 8, 96, 10, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 45 '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', 46 False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 47 } 48 __gproperties = __gproperties__ 49 50 __gsignals__ = { 51 'exit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()), 52 } 53 54 def __init__(self, *a, **kw): 55 gtk.VBox.__init__(self, *a, **kw) 56 self.use_double_click = False 57 self.gstat = GStat() 58 # if 'NO_FORCE_HOMING' is true, MDI commands are allowed before homing. 59 inifile = os.environ.get('INI_FILE_NAME', '/dev/null') 60 self.ini = linuxcnc.ini(inifile) 61 self.no_home_required = int(self.ini.find("TRAJ", "NO_FORCE_HOMING") or 0) 62 path = self.ini.find('DISPLAY', 'MDI_HISTORY_FILE') or '~/.axis_mdi_history' 63 self.filename = os.path.expanduser(path) 64 65 self.model = gtk.ListStore(str) 66 67 self.tv = gtk.TreeView() 68 self.default_font = self.tv.get_style().font_desc.to_string() 69 self.tv.modify_font(pango.FontDescription(self.default_font)) 70 self.tv.set_model(self.model) 71 self.cell = gtk.CellRendererText() 72 73 self.col = gtk.TreeViewColumn("Command") 74 self.col.pack_start(self.cell, True) 75 self.col.add_attribute(self.cell, 'text', 0) 76 77 self.tv.append_column(self.col) 78 self.tv.set_search_column(0) 79 self.tv.set_reorderable(False) 80 self.tv.set_headers_visible(True) 81 self.tv.get_selection().set_mode(gtk.SELECTION_NONE) 82 83 scroll = gtk.ScrolledWindow() 84 scroll.add(self.tv) 85 scroll.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC 86 scroll.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC 87 88 self.entry = gtk.Entry() 89 self.entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, 'gtk-ok') 90 self.entry.modify_font(pango.FontDescription(self.default_font)) 91 92 self.entry.connect('activate', self.submit) 93 self.entry.connect('icon-press', self.submit) 94 self.tv.connect('cursor-changed', self.select) 95 self.tv.connect('key_press_event', self.on_key_press_event) 96 self.connect('key_press_event', self.on_key_press_event) 97 self.tv.connect('button_press_event', self.on_button_press_event) 98 99 self.pack_start(scroll, True) 100 self.pack_start(self.entry, False) 101 self.gstat.connect('state-off', lambda w: self.set_sensitive(False)) 102 self.gstat.connect('state-estop', lambda w: self.set_sensitive(False)) 103 self.gstat.connect('interp-idle', lambda w: self.set_sensitive(self.machine_on())) 104 self.gstat.connect('interp-run', lambda w: self.set_sensitive(not self.is_auto_mode())) 105 self.gstat.connect('all-homed', lambda w: self.set_sensitive(self.machine_on())) 106 # this time lambda with two parameters, as not all homed will send also the unhomed joints 107 self.gstat.connect('not-all-homed', lambda w,uj: self.set_sensitive(self.no_home_required) ) 108 self.reload() 109 self.show_all() 110 111 def reload(self): 112 self.model.clear() 113 114 try: 115 fp = open(self.filename) 116 except: 117 return 118 lines = map(str.strip, fp.readlines()) 119 fp.close() 120 121 lines = filter(bool, lines) 122 for l in lines: 123 self.model.append((l,)) 124 path = (len(lines)-1,) 125 self.tv.scroll_to_cell(path) 126 self.tv.set_cursor(path) 127 self.entry.set_text('') 128 129 def submit(self, *a): 130 cmd = self.entry.get_text() 131 if cmd == 'HALMETER' or cmd == 'halmeter': 132 self.load_halmeter() 133 return 134 elif cmd == 'STATUS' or cmd == 'status': 135 self.load_status() 136 return 137 elif cmd == 'HALSHOW' or cmd == 'halshow': 138 self.load_halshow() 139 return 140 if not cmd: 141 return 142 ensure_mode(self.gstat.stat, self.linuxcnc, linuxcnc.MODE_MDI) 143 144 self.linuxcnc.mdi(cmd) 145 self.entry.set_text('') 146 self.entry.grab_focus() 147 148 add_to_file = True 149 # we need to put this in a try, because if the hal mdi history file is 150 # empty, the model is empty and we will get an None iter! 151 try: 152 actual = self.tv.get_cursor()[0] 153 iter = self.model.get_iter(actual) 154 old_cmd = self.model.get_value(iter,0) 155 156 lastiter = self._get_iter_last(self.model) 157 len = int(self.model.get_string_from_iter(lastiter)) 158 159 if actual[0] == len and old_cmd == cmd: 160 add_to_file = False 161 except: 162 pass 163 164 if add_to_file: 165 try: 166 fp = open(self.filename, 'a') 167 fp.write(cmd + "\n") 168 fp.close() 169 except: 170 pass 171 172 last = self.model.append((cmd,)) 173 path = self.model.get_path(last) 174 self.tv.scroll_to_cell(path) 175 self.tv.set_cursor(path) 176 self.entry.set_text('') 177 self.entry.grab_focus() 178 self.tv.get_selection().set_mode(gtk.SELECTION_NONE) 179 180 def select(self, w): 181 idx = w.get_cursor()[0] 182 if idx is None: 183 return True 184 self.entry.set_text(self.model[idx][0]) 185 self.entry.grab_focus() 186 self.entry.set_position(-1) 187 188 def on_key_press_event(self,w,event): 189 # get the keyname 190 keyname = gtk.gdk.keyval_name(event.keyval) 191 # print(keyname) 192 idx = self.tv.get_cursor()[0] 193 if idx is None: 194 return True 195 196 lastiter = self._get_iter_last(self.model) 197 len = int(self.model.get_string_from_iter(lastiter)) 198 199 #if nothing is selected, we need to activate the last one on up key 200 selection = self.tv.get_selection() 201 _, selected = selection.get_selected() 202 203 204 if keyname == 'Up': 205 self.tv.get_selection().set_mode(gtk.SELECTION_SINGLE) 206 if not selected: 207 self.tv.set_cursor(len) 208 else: 209 if idx[0] > 0: 210 self.tv.set_cursor(idx[0] - 1) 211 else: 212 self.tv.set_cursor(idx[0]) 213 return True 214 215 if keyname == 'Down': 216 if not selected: 217 return True 218 self.tv.get_selection().set_mode(gtk.SELECTION_SINGLE) 219 if idx[0] < len: 220 self.tv.set_cursor(idx[0] + 1) 221 else: 222 self.tv.set_cursor(idx[0]) 223 self.entry.set_text('') 224 self.entry.grab_focus() 225 self.tv.get_selection().set_mode(gtk.SELECTION_NONE) 226 return True 227 228 if keyname == 'Escape': 229 self.entry.set_text('') 230 self.entry.grab_focus() 231 self.tv.get_selection().set_mode(gtk.SELECTION_NONE) 232 233 def on_button_press_event(self,w,event): 234 idx = w.get_cursor()[0] 235 if idx is None: 236 return True 237 self.tv.get_selection().set_mode(gtk.SELECTION_SINGLE) 238 self.entry.set_text(self.model[idx][0]) 239 self.entry.grab_focus() 240 self.entry.set_position(-1) 241 if event.type == gtk.gdk._2BUTTON_PRESS: 242 print("Double Click", self.use_double_click) 243 if self.use_double_click: 244 self.submit() 245 246 def load_halmeter(self): 247 p = os.popen("halmeter &") 248 def load_status(self): 249 p = os.popen("linuxcnctop > /dev/null &","w") 250 def load_halshow(self): 251 try: 252 p = os.popen("tclsh %s/bin/halshow.tcl &" % (TCLPATH)) 253 except: 254 self.entry.set_text('ERROR loading halshow') 255 256 def _get_iter_last(self, model): 257 itr = model.get_iter_first() 258 last = None 259 while itr: 260 last = itr 261 itr = model.iter_next(itr) 262 return last 263 264 def _change_font_entry(self, value): 265 font = self.default_font.split()[0] 266 new_font = font +" " + str(value) 267 self.entry.modify_font(pango.FontDescription(new_font)) 268 269 def _change_font_tree(self, value): 270 font = self.default_font.split()[0] 271 new_font = font +" " + str(value) 272 self.tv.modify_font(pango.FontDescription(new_font)) 273 274 # Get property 275 def do_get_property(self, property): 276 name = property.name.replace('-', '_') 277 if name in self.__gproperties.keys(): 278 return getattr(self, name) 279 else: 280 raise AttributeError('unknown property %s' % property.name) 281 282 # Set property 283 def do_set_property(self, property, value): 284 try: 285 name = property.name.replace('-', '_') 286 if name in self.__gproperties.keys(): 287 setattr(self, name, value) 288 self.queue_draw() 289 if name == "font_size_tree": 290 self._change_font_tree(value) 291 if name == "font_size_entry": 292 self._change_font_entry(value) 293 if name == "use_double_click": 294 self.use_double_click = value 295 else: 296 raise AttributeError('unknown property %s' % property.name) 297 except: 298 pass 299 300 # for testing without glade editor or LinuxCNC not running: 301 def main(): 302 window = gtk.Window(gtk.WINDOW_TOPLEVEL) 303 mdi = EMC_MDIHistory() 304 mdi.set_property("font_size_tree", 12) 305 mdi.set_property("font_size_entry", 20) 306 mdi.set_property("use_double_click", True) 307 window.add(mdi) 308 window.connect("destroy", gtk.main_quit) 309 window.set_size_request(250, 400) 310 window.show_all() 311 gtk.main() 312 313 if __name__ == "__main__": 314 main()