Index: /palm/trunk/SCRIPTS/palm_jm
===================================================================
--- /palm/trunk/SCRIPTS/palm_jm	(revision 2065)
+++ /palm/trunk/SCRIPTS/palm_jm	(revision 2065)
@@ -0,0 +1,393 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#--------------------------------------------------------------------------------#
+# This file is part of PALM.
+#
+# PALM is free software: you can redistribute it and/or modify it under the terms
+# of the GNU General Public License as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later version.
+#
+# PALM is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PALM. If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright 1997-2016  Leibniz Universitaet Hannover
+#--------------------------------------------------------------------------------#
+#
+# Current revisions:
+# -----------------
+# Initial revision
+# 
+# Former revisions:
+# -----------------
+# $Id$
+#
+# 
+#
+# Description:
+# ------------
+# PALM job manager works a file manager for PALM input files. It lists all jobs in
+# the working directory and displays associated input and user code files, which
+# can be opened directly from the interface. Also, it is possible to copy jobs and
+# to create entire new sets of jobs based on a reference job.
+# 
+# Instructions:
+# -------------
+# 1) Start palm_jm either from mrungui or from shell by "nohup palm_jm&"
+#
+# To do:
+# ------
+# 1) Add display for the MONITORING and OUTPUT folder
+# 2) Add zip function to export single or multiple jobs
+# 3) Allow for copying INPUT files to a remote host
+#------------------------------------------------------------------------------!
+
+import os
+from PyQt4 import QtGui, QtCore, uic
+from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT
+import shutil
+import subprocess as sub
+import sys
+
+set_list = []
+
+
+# Determine PALM directories
+try: 
+   devnull = open(os.devnull, 'w')
+   out = sub.check_output("echo $PALM_BIN", shell=True, stderr=sub.STDOUT)
+   palm_bin = out.rstrip()
+   palm_dir = out.split("palm")[0] + "palm/" + out.split("palm")[1].split("/")[1]
+   out = None
+   
+   # remove this line on release
+#   palm_dir = '/home/maronga/PUB/thinktank/maronga/job_manager'
+   
+   job_dir = palm_dir + '/JOBS'
+   user_dir = palm_dir + '/USER_CODE'
+   
+except:   
+   print "Error. $PALM_BIN is not set."
+   raise SystemExit
+
+
+
+# MainWindow class
+class JobManager(QtGui.QMainWindow):
+    
+    def __init__(self):
+        super(JobManager, self).__init__()
+        
+        self.InitUi()   
+        
+   
+    # Initialized MainWindow UI
+    def InitUi(self):
+
+        # Load predefined mainwindow
+        uic.loadUi('palm_jm_files/palm_jm.ui', self)
+    
+        # Display MainWindow
+        self.show()
+        QtGui.QApplication.processEvents()
+
+        self.load_jobs()
+
+    
+    def update_all(self):
+ 
+       self.setEnabled(False)
+       self.list_input.clear() 
+       self.list_user.clear()
+       self.load_jobs()
+       self.setEnabled(True)
+
+    # Load jobs into list
+    def load_jobs(self):
+       
+       self.list_jobs.clear() 
+       self.push_copy.setEnabled(False)
+       self.push_create_set.setEnabled(False)      
+      
+       self.line_path.setText(job_dir + "/")
+       
+       list_of_files = os.listdir(job_dir)
+             
+       for i in range(0,len(list_of_files)):
+          tmp_file = job_dir + "/" + list_of_files[i]
+
+          if ( os.path.isdir(tmp_file) ):
+	     self.list_jobs.addItem(str(list_of_files[i]))
+
+
+    # Update input and user code lists
+    def update_input(self):
+      
+       self.list_input.clear() 
+       self.list_user.clear()
+       self.push_copy.setEnabled(True)
+       self.push_create_set.setEnabled(True)
+      
+       job_to_show = job_dir + "/" + self.list_jobs.currentItem().text() + "/INPUT"
+
+       if ( os.path.isdir(job_to_show) ):
+
+          list_of_files = os.listdir(job_to_show)
+         
+          for i in range(0,len(list_of_files)):
+             tmp_file = job_to_show + "/" + list_of_files[i]
+             
+             if ( os.path.isfile(tmp_file) ):
+	        self.list_input.addItem(str(list_of_files[i]))
+  
+       job_to_show = user_dir + "/" + self.list_jobs.currentItem().text()
+       
+       if ( os.path.isdir(job_to_show) ):
+          
+          list_of_files = os.listdir(job_to_show)
+          
+          for i in range(0,len(list_of_files)):
+             tmp_file = job_to_show + "/" + list_of_files[i]
+
+             if ( os.path.isfile(tmp_file) ):
+	        self.list_user.addItem(str(list_of_files[i]))
+  
+  
+    # Make a copy of a job
+    def copy_job(self):
+  
+       self.setEnabled(False)
+       old_job_name = self.list_jobs.currentItem().text()
+       
+       text, ret = QtGui.QInputDialog.getText(self, "Copy job", "Enter new job name:", mode = QtGui.QLineEdit.Normal, text = old_job_name)
+       
+       if ( ret ):
+          new_job_name = str(text)
+       else:
+	   self.setEnabled(True)
+	   return
+	 
+       new_input_dir  = job_dir + "/" + new_job_name + "/INPUT"
+
+#      check if a job exists with the new job name  
+       if ( os.path.isdir(new_input_dir) ):
+	   notify = QtGui.QMessageBox.warning(self,'Create new job directory',"Error. Could not create job directory. A job with the new name already exists.")
+	   self.setEnabled(True)
+	   return
+       else:
+	   os.makedirs(new_input_dir)
+
+#      copy and rename input files (if present)       
+       job_to_copy = job_dir + "/" + old_job_name + "/INPUT"
+
+       if ( os.path.isdir(job_to_copy) ):
+
+          list_of_files = os.listdir(job_to_copy)
+         
+          for i in range(0,len(list_of_files)):
+
+             tmp_file = job_to_copy + "/" + list_of_files[i]          
+             new_file = new_input_dir + "/" + list_of_files[i].replace(old_job_name, new_job_name)
+             shutil.copy(tmp_file, new_file)
+
+#      copy and rename user code (if present)
+       user_code_to_copy = user_dir + "/" + old_job_name
+       new_user_code_dir = user_dir + "/" + new_job_name
+
+#      check if a job exists with the new job name  
+       if ( os.path.isdir(new_user_code_dir) ):
+	   notify = QtGui.QMessageBox.warning(self,'Create new user code directory',"Error. Could not create user code directory. A user code directory with the new name already exists.")
+	   self.setEnabled(True)
+	   return
+       else:
+	   os.makedirs(new_user_code_dir)
+
+       if ( os.path.isdir(user_code_to_copy) ):
+
+          list_of_files = os.listdir(user_code_to_copy)
+         
+          for i in range(0,len(list_of_files)):
+
+             tmp_file = user_code_to_copy + "/" + list_of_files[i]        
+             new_file = new_user_code_dir + "/" + list_of_files[i]
+             shutil.copy(tmp_file, new_file)
+
+       self.load_jobs()
+       self.list_input.clear() 
+       self.list_user.clear()
+       self.setEnabled(True)
+
+
+    # Create a whole set of jobs
+    def create_set(self):
+  
+       global set_list
+#      disable mainwindow  
+       self.setEnabled(False)
+      
+#      show Options Dialog      
+       opt = CreateSetBox()
+       opt.exec_()
+
+       old_job_name = self.list_jobs.currentItem().text()
+
+       for j in range(0,len(set_list)):
+
+          if ( set_list[j] != "" ):
+	     new_job_name   = str(set_list[j])
+             new_input_dir  = job_dir + "/" + str(set_list[j]) + "/INPUT"
+          else:
+	     continue
+
+#         check if a job exists with the new job name  
+          if ( os.path.isdir(new_input_dir) ):
+	     notify = QtGui.QMessageBox.warning(self,'Create new job directory',"Error. Could not create job directory. A job with the new name already exists.")
+	     self.setEnabled(True)
+	     return
+          else:
+	     os.makedirs(new_input_dir)
+
+#         copy and rename input files (if present)       
+          job_to_copy = job_dir + "/" + old_job_name + "/INPUT"
+
+          if ( os.path.isdir(job_to_copy) ):
+
+             list_of_files = os.listdir(job_to_copy)
+         
+             for i in range(0,len(list_of_files)):
+
+                tmp_file = job_to_copy + "/" + list_of_files[i]          
+                new_file = new_input_dir + "/" + list_of_files[i].replace(old_job_name, new_job_name)
+                shutil.copy(tmp_file, new_file)
+
+#         copy and rename user code (if present)
+          user_code_to_copy = user_dir + "/" + old_job_name
+          new_user_code_dir = user_dir + "/" + new_job_name
+
+#         check if a job exists with the new job name  
+          if ( os.path.isdir(new_user_code_dir) ):
+	     notify = QtGui.QMessageBox.warning(self,'Create new user code directory',"Error. Could not create user code directory. A user code directory with the new name already exists.")
+	     self.setEnabled(True)
+	     return
+          else:
+	     os.makedirs(new_user_code_dir)
+
+          if ( os.path.isdir(user_code_to_copy) ):
+
+             list_of_files = os.listdir(user_code_to_copy)
+         
+             for i in range(0,len(list_of_files)):
+
+                tmp_file = user_code_to_copy + "/" + list_of_files[i]        
+                new_file = new_user_code_dir + "/" + list_of_files[i]
+                shutil.copy(tmp_file, new_file)
+
+          self.load_jobs()
+          self.list_input.clear() 
+          self.list_user.clear()
+
+       self.setEnabled(True) 
+   
+   
+ # Add a custom context menu
+    def openmenuinput(self, position):
+
+        menu = QtGui.QMenu()
+
+        selection = self.list_input.selectedItems()
+        
+        if ( len(selection) != 0 ):
+
+           openAction = menu.addAction('Open selected files')     
+           openAction.setStatusTip('Open file(s) in your favorite editor')
+           openAction.triggered.connect(self.OpenFilesInput)
+           action = menu.exec_(self.list_input.mapToGlobal(position))
+
+ # Add a custom context menu
+    def openmenuuser(self, position):
+
+        menu = QtGui.QMenu()
+
+        selection = self.list_user.selectedItems()
+        
+        if ( len(selection) != 0 ):
+
+           openAction = menu.addAction('Open selected files')     
+           openAction.setStatusTip('Open file(s) in your favorite editor')
+           openAction.triggered.connect(self.OpenFilesUser)
+           action = menu.exec_(self.list_user.mapToGlobal(position))
+
+
+    def OpenFilesInput(self):
+   
+       sel_job = self.list_jobs.currentItem().text()
+       sel_files = self.list_input.selectedItems()
+       
+       input_dir = job_dir + "/" + sel_job + "/INPUT/"
+       
+       open_files = ""
+       for i in range(0,len(sel_files)):
+          open_files = open_files + "xdg-open " + input_dir +  sel_files[i].text() + "; "
+
+       os.system(str(open_files))
+
+    def OpenFilesUser(self):
+   
+       sel_job = self.list_jobs.currentItem().text()
+       sel_files = self.list_user.selectedItems()
+       
+       input_dir = user_dir + "/" + sel_job + "/"
+       
+       open_files = ""
+       for i in range(0,len(sel_files)):
+          open_files = open_files + "xdg-open " + input_dir +  sel_files[i].text() + "; "
+
+       os.system(str(open_files)) 
+       selection = self.list_jobs.selectedItems()
+  
+  
+  
+# Message box for showing RUN_CONTROL output
+class CreateSetBox(QtGui.QDialog):
+    def __init__(self):
+      
+        super(CreateSetBox, self).__init__()
+        
+        uic.loadUi('palm_jm_files/palm_jm_create_set.ui', self)
+        
+        self.show()
+        
+        return
+
+#   Cancel button
+    def rejected(self):
+      
+       self.close()
+     
+       return
+  
+
+#   OK button
+    def accept(self):
+      
+       global set_list
+       
+       text = self.list.toPlainText() 
+       set_list = text.split('\n')
+       self.close()
+     
+       return
+  
+# Main loop       
+def main():
+    
+    app = QtGui.QApplication(sys.argv)
+    res = JobManager() 
+    sys.exit(app.exec_())
+
+
+if __name__ == '__main__':
+    main()   
Index: /palm/trunk/SCRIPTS/palm_jm_files/palm_jm.ui
===================================================================
--- /palm/trunk/SCRIPTS/palm_jm_files/palm_jm.ui	(revision 2065)
+++ /palm/trunk/SCRIPTS/palm_jm_files/palm_jm.ui	(revision 2065)
@@ -0,0 +1,337 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>735</width>
+    <height>456</height>
+   </rect>
+  </property>
+  <property name="contextMenuPolicy">
+   <enum>Qt::NoContextMenu</enum>
+  </property>
+  <property name="windowTitle">
+   <string>PALM job manager</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QLineEdit" name="line_path">
+    <property name="enabled">
+     <bool>true</bool>
+    </property>
+    <property name="geometry">
+     <rect>
+      <x>139</x>
+      <y>28</y>
+      <width>584</width>
+      <height>23</height>
+     </rect>
+    </property>
+    <property name="readOnly">
+     <bool>true</bool>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="push_copy">
+    <property name="geometry">
+     <rect>
+      <x>276</x>
+      <y>335</y>
+      <width>114</width>
+      <height>24</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Copy</string>
+    </property>
+   </widget>
+   <widget class="QListWidget" name="list_input">
+    <property name="geometry">
+     <rect>
+      <x>271</x>
+      <y>71</y>
+      <width>220</width>
+      <height>257</height>
+     </rect>
+    </property>
+    <property name="contextMenuPolicy">
+     <enum>Qt::CustomContextMenu</enum>
+    </property>
+    <property name="alternatingRowColors">
+     <bool>true</bool>
+    </property>
+    <property name="selectionMode">
+     <enum>QAbstractItemView::MultiSelection</enum>
+    </property>
+    <property name="sortingEnabled">
+     <bool>true</bool>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label">
+    <property name="geometry">
+     <rect>
+      <x>282</x>
+      <y>55</y>
+      <width>74</width>
+      <height>16</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>INPUT files:</string>
+    </property>
+   </widget>
+   <widget class="QListWidget" name="list_user">
+    <property name="geometry">
+     <rect>
+      <x>504</x>
+      <y>73</y>
+      <width>220</width>
+      <height>257</height>
+     </rect>
+    </property>
+    <property name="contextMenuPolicy">
+     <enum>Qt::CustomContextMenu</enum>
+    </property>
+    <property name="alternatingRowColors">
+     <bool>true</bool>
+    </property>
+    <property name="selectionMode">
+     <enum>QAbstractItemView::MultiSelection</enum>
+    </property>
+    <property name="sortingEnabled">
+     <bool>true</bool>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_2">
+    <property name="geometry">
+     <rect>
+      <x>510</x>
+      <y>55</y>
+      <width>156</width>
+      <height>16</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>USER_CODE files:</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="push_create_set">
+    <property name="geometry">
+     <rect>
+      <x>275</x>
+      <y>359</y>
+      <width>114</width>
+      <height>24</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Create Set</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="push_close">
+    <property name="geometry">
+     <rect>
+      <x>275</x>
+      <y>384</y>
+      <width>115</width>
+      <height>24</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Quit</string>
+    </property>
+   </widget>
+   <widget class="QListWidget" name="list_jobs">
+    <property name="geometry">
+     <rect>
+      <x>9</x>
+      <y>73</y>
+      <width>256</width>
+      <height>338</height>
+     </rect>
+    </property>
+    <property name="contextMenuPolicy">
+     <enum>Qt::NoContextMenu</enum>
+    </property>
+    <property name="sortingEnabled">
+     <bool>true</bool>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_3">
+    <property name="geometry">
+     <rect>
+      <x>15</x>
+      <y>55</y>
+      <width>121</width>
+      <height>16</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Available jobs:</string>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_4">
+    <property name="geometry">
+     <rect>
+      <x>145</x>
+      <y>11</y>
+      <width>145</width>
+      <height>16</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Job directory:</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="push_update">
+    <property name="geometry">
+     <rect>
+      <x>10</x>
+      <y>29</y>
+      <width>128</width>
+      <height>24</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Update job list</string>
+    </property>
+   </widget>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>735</width>
+     <height>21</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>push_close</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>319</x>
+     <y>418</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>429</x>
+     <y>442</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>list_jobs</sender>
+   <signal>itemActivated(QListWidgetItem*)</signal>
+   <receiver>MainWindow</receiver>
+   <slot>update_input()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>196</x>
+     <y>290</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>384</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>push_copy</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>copy_job()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>341</x>
+     <y>366</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>409</x>
+     <y>368</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>push_create_set</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>create_set()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>340</x>
+     <y>389</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>488</x>
+     <y>418</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>push_update</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>update_all()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>85</x>
+     <y>57</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>654</x>
+     <y>385</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>list_input</sender>
+   <signal>customContextMenuRequested(QPoint)</signal>
+   <receiver>MainWindow</receiver>
+   <slot>openmenuinput()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>389</x>
+     <y>110</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>448</x>
+     <y>29</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>list_user</sender>
+   <signal>customContextMenuRequested(QPoint)</signal>
+   <receiver>MainWindow</receiver>
+   <slot>openmenuuser()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>570</x>
+     <y>127</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>615</x>
+     <y>36</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>update_input()</slot>
+  <slot>copy_job()</slot>
+  <slot>rename_job()</slot>
+  <slot>create_set()</slot>
+  <slot>update_all()</slot>
+  <slot>openmenuinput()</slot>
+  <slot>openmenuuser()</slot>
+ </slots>
+</ui>
Index: /palm/trunk/SCRIPTS/palm_jm_files/palm_jm_create_set.ui
===================================================================
--- /palm/trunk/SCRIPTS/palm_jm_files/palm_jm_create_set.ui	(revision 2065)
+++ /palm/trunk/SCRIPTS/palm_jm_files/palm_jm_create_set.ui	(revision 2065)
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>280</width>
+    <height>281</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Create job set</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>245</y>
+     <width>256</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation">
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons">
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label">
+   <property name="geometry">
+    <rect>
+     <x>15</x>
+     <y>10</y>
+     <width>261</width>
+     <height>16</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Enter new job names (one job per line):</string>
+   </property>
+  </widget>
+  <widget class="QTextEdit" name="list">
+   <property name="geometry">
+    <rect>
+     <x>11</x>
+     <y>32</y>
+     <width>257</width>
+     <height>216</height>
+    </rect>
+   </property>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>Dialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>Dialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>270</x>
+     <y>265</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
