/ lib / python / gladevcp / hal_mdihistory.py
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()