summaryrefslogtreecommitdiffstats
path: root/build_tools/larch7/larch0/gui
diff options
context:
space:
mode:
Diffstat (limited to 'build_tools/larch7/larch0/gui')
-rw-r--r--build_tools/larch7/larch0/gui/MEDIUM_README14
-rwxr-xr-xbuild_tools/larch7/larch0/gui/askpass.py35
-rw-r--r--build_tools/larch7/larch0/gui/controller.py466
-rwxr-xr-xbuild_tools/larch7/larch0/gui/dirview.py172
-rwxr-xr-xbuild_tools/larch7/larch0/gui/dispatcher.py108
-rw-r--r--build_tools/larch7/larch0/gui/front/docviewer.py68
-rw-r--r--build_tools/larch7/larch0/gui/front/editor.py115
-rw-r--r--build_tools/larch7/larch0/gui/front/logview.py94
-rw-r--r--build_tools/larch7/larch0/gui/front/mainwindow.py132
-rw-r--r--build_tools/larch7/larch0/gui/front/page_installation.py188
-rw-r--r--build_tools/larch7/larch0/gui/front/page_larchify.py248
-rw-r--r--build_tools/larch7/larch0/gui/front/page_medium.py320
-rw-r--r--build_tools/larch7/larch0/gui/front/page_mediumprofile.py87
-rw-r--r--build_tools/larch7/larch0/gui/front/page_project.py203
-rw-r--r--build_tools/larch7/larch0/gui/front/uim.py1327
-rwxr-xr-xbuild_tools/larch7/larch0/gui/larch.py38
-rw-r--r--build_tools/larch7/larch0/gui/layouts/docviewer.uim72
-rw-r--r--build_tools/larch7/larch0/gui/layouts/editor.uim92
-rw-r--r--build_tools/larch7/larch0/gui/layouts/logger.uim57
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_installation.uim179
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_larchify.uim177
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_main.uim101
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_medium.uim271
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_mediumprofile.uim99
-rw-r--r--build_tools/larch7/larch0/gui/layouts/page_project.uim181
-rw-r--r--build_tools/larch7/larch0/gui/layouts/progress.uim64
-rw-r--r--build_tools/larch7/larch0/gui/project.py428
27 files changed, 5336 insertions, 0 deletions
diff --git a/build_tools/larch7/larch0/gui/MEDIUM_README b/build_tools/larch7/larch0/gui/MEDIUM_README
new file mode 100644
index 0000000..ede0975
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/MEDIUM_README
@@ -0,0 +1,14 @@
+Medium building
+
+In the cli, when -S--source is passed, no chroot is used for building unless
+explicitly requested via the -C/--chroot option. When no -S--source is passed,
+chroot will be used unless the -c/--nochroot option is passed.
+
+In the gui the default source is the medium directory within the installation
+dir (as built by the previous stages).
+If this is selected chroot commands will be used for the
+medium building unless explicitly overridden (uncheck the chroot CheckBox).
+If another source is selected chroot commands will only be used if explicitly
+requested (check the chroot CheckBox). The chroot dir would be the project's
+installation dir (so if this is '/' chroot is never available).
+
diff --git a/build_tools/larch7/larch0/gui/askpass.py b/build_tools/larch7/larch0/gui/askpass.py
new file mode 100755
index 0000000..ac65325
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/askpass.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+"""
+# One possibility:
+from PyQt4 import QtGui
+
+app = QtGui.QApplication([])
+result, ok = QtGui.QInputDialog.getText(None, "sudo",
+ "Please enter the password to run as administrator",
+ QtGui.QLineEdit.Password)
+
+
+print result
+#exit(0 if ok else 1)
+"""
+
+# This version connects via a socket to the main application
+import socket
+import sys
+
+port = '\0larch-sudopw'
+data = 'pw-get'
+
+# Create a socket (SOCK_STREAM means a TCP socket)
+sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+
+# Connect to server and send data
+sock.connect(port)
+sock.send(data + '\n')
+
+# Receive data from the server and shut down
+received = sock.recv(1024)
+sock.close()
+
+print received
diff --git a/build_tools/larch7/larch0/gui/controller.py b/build_tools/larch7/larch0/gui/controller.py
new file mode 100644
index 0000000..2025301
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/controller.py
@@ -0,0 +1,466 @@
+#!/usr/bin/env python
+#
+# controller.py - Manages file-system access and calling of larch scripts
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+#2010.07.19
+
+import sys, os, pwd, traceback, __builtin__
+try:
+ import json as serialize
+except:
+ import simplejson as serialize
+from subprocess import Popen, PIPE, call
+import threading
+import re
+import SocketServer
+
+from config import *
+start_translator(switchC=False)
+
+
+exports = {}
+def add_exports(elist):
+ for key, method in elist:
+ exports[key] = method
+
+__builtin__.add_exports = add_exports
+
+
+def error0(message):
+ sys.stderr.write('>>ERROR>>' + message + '\n')
+ sys.stderr.flush()
+__builtin__.error0 = error0
+
+
+class Fs:
+ """Collect file system access methods in one class.
+ """
+ def __init__(self):
+ add_exports( (
+ ('fetch_layout', self.fetch_layout),
+ ('isfile', self.isfile),
+ ('isdir', self.isdir),
+ ('rm_rf', self.rm_rf),
+ ('get_partitions', self.get_partitions),
+ ('readfile', self.readfile),
+ ('savefile', self.savefile),
+ ('get_docs_url', self.get_docs_url),
+ ('oldsqf', self.oldsqf),
+ ('oldlocales', self.oldlocales),
+# ('getpath', self.getpath),
+ ('browse', self.browse))
+ )
+
+ def fetch_layout(self, layout_file):
+ fh = open(base_dir + '/gui/layouts/' + layout_file)
+ r = fh.read()
+ fh.close()
+ return (True, eval(r))
+
+ def rm_rf(self, path):
+ call(['rm', '-rf', self._getpath(path)])
+ return (True, None)
+
+
+# def exists(self, path):
+# return os.path.exists(self._getpath(path))
+
+# def copy(self, src, dst):
+# shutil.copytree(self._getpath(src), self._getpath(dst))
+
+
+ def oldsqf(self):
+ return (True,
+ os.path.isfile(self._getpath('install:' + CHROOT_SYSTEMSQF))
+ or os.path.isfile(self._getpath('install:' + CHROOT_DIR_MEDIUM
+ + '/larch/system.sqf')))
+
+ def oldlocales(self):
+ return (True, os.path.isdir(self._getpath('install:%s/locale'
+ % CHROOT_DIR_BUILD)))
+
+ def isfile(self, path):
+ return (True, os.path.isfile(self._getpath(path)))
+
+ def isdir(self, path):
+ return (True, os.path.isdir(self._getpath(path)))
+
+
+ def browse(self, path):
+ fpath = self._getpath(path)
+ if call(['mkdir', '-p', fpath]) == 0:
+ # Start file browser at fpath
+ call(project_manager.appget('filebrowser').replace('$', fpath)
+ + ' &', shell=True)
+ return (True, None)
+ else:
+ return (False, None)
+
+
+# def makedirs(self, path):
+# os.makedirs(self._getpath(path))
+# return (True, None)
+
+ def readfile(self, f):
+ f = self._getpath(f)
+ try:
+ fh = open(f)
+ r = fh.read()
+ fh.close()
+ return (True, r)
+ except:
+ return (False, _("Couldn't read file '%s'") % f)
+
+ def savefile(self, f, d):
+ f = self._getpath(f)
+ dir = os.path.dirname(f)
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+ try:
+ fh = open(f, "w")
+ fh.write(d)
+ fh.close()
+ return (True, None)
+ except:
+ return (False, _("Couldn't save file '%s'") % f)
+
+ def _getpath(self, f):
+ if f[0] != "/":
+ base, f = f.split(':')
+ f = '/' + f
+ if base == 'base':
+ f = base_dir + f
+ elif base == 'profile':
+ f = project_manager.profile_path + f
+ elif base == 'working':
+ f = project_manager.project_dir + f
+ else:
+ f = project_manager.get_ipath()[1] + f
+ return f
+
+ def get_docs_url(self, page):
+ if lang and (len(lang) > 1):
+ p = base_dir + ('/docs/%s/html/' % lang[0:2]) + page
+ if os.path.isfile(p):
+ return (True, p)
+ return (True, base_dir + '/docs/html/' + page)
+
+
+ def get_partitions(self):
+ """Get a list of available partitions (only unmounted ones
+ are included).
+ """
+ # First get a list of mounted devices
+ mounteds = []
+ fh = open('/etc/mtab')
+ for l in fh:
+ dev = l.split()[0]
+ if dev.startswith('/dev/sd'):
+ mounteds.append(dev[5:])
+ fh.close()
+ # Get a list of partitions
+ partlist = []
+ fh = open('/proc/partitions')
+ for l in fh:
+ fields = l.split()
+ if len(fields) == 4:
+ dev = fields[3]
+ if dev.startswith('sd') and (dev[-1] in '0123456789'):
+ size = (int(fields[2]) + 512) / 1024
+ if (size > 200) and (dev not in mounteds):
+ # Keep a tuple (partition, size in MiB)
+ partlist.append("%-12s %12s MiB"
+ % ('/dev/' + dev, size))
+ fh.close()
+ return (True, partlist)
+
+
+
+class LarchScripts:
+ """This class deals with calling the larch scripts.
+ As they must be run as root a separate dispatcher process, running as
+ root, is used to call the actual scripts. The dispatcher is started
+ using 'sudo'.
+ A call will be initiated from the gui, and is sent to the dispatcher,
+ which starts the process and returns the output when it is available.
+ If the script is interactive, it might also require input, which can be
+ passed via the dispatcher.
+ While reading output from the dispatcher the gui must remain responsive,
+ so that the view can be switched and the subprocess aborted, if desired.
+ To achieve this a separate thread is used for reading input from the
+ dispatcher, together with a mechanism for activating a handler in a
+ thread-safe way, 'ui.idle_add'.
+ """
+ def __init__(self):
+ self.larch_dispatcher = None # dispatcher subprocess
+ self.progress_waiting = None # used by progress widget
+
+
+ def call(self, cmd, arg=[], atend=None):
+ self.cmd = cmd
+ self.arg = arg
+ # Callback on completion:
+ self.atend = atend # returns True for automatic return to normal view
+ if self.larch_dispatcher:
+ self.runcmd()
+
+ else:
+ # Start a socket server to handle password requests
+ # Use a unix domain server in the abstract namespace
+ port = '\0larch-sudopw'
+ # Create the server
+ self.sserver = SocketServer.UnixStreamServer(port, MyHandler)
+ self.sst = threading.Thread(target=self.sserver.serve_forever,
+ args=())
+ self.sst.start()
+ # Handle one request
+ #self.sserver.handle_request()
+
+ # Start the larch dispatcher script
+ os.environ['SUDO_ASKPASS'] = base_dir + '/gui/askpass.py'
+ self._sudo_wait()
+ dispatcher = Popen(['sudo', '-A', '-k',
+ base_dir + '/gui/dispatcher.py'],
+ stdin=PIPE,
+ stdout=PIPE,
+ stderr=PIPE)
+
+ # And a thread to read its output
+ self.istream = dispatcher.stdin
+ self.estream = dispatcher.stderr
+ self.t = threading.Thread(target=self.readinput, args=(dispatcher,))
+ self.t.start()
+
+
+ def runcmd(self):
+ progress.start() # initialize progress widget
+ ui.runningtab(1) # switch view to progress widget
+ # Run command:
+ cx = '%s %s:%s\n' % (self.cmd, project_manager.project_dir,
+ serialize.dumps(self.arg))
+ logger.addLine('++' + cx)
+ self.istream.write(cx)
+ self.istream.flush()
+ r = self.geterr()
+ if r:
+ ui.command('infoDialog', r, 'BUG')
+
+
+ def geterr(self):
+ r = ""
+ while True:
+ rx = self.estream.readline()
+ if rx:
+ rx = rx.strip()
+ else:
+ break
+ if rx == '!+':
+ break
+ if r:
+ r +='\n'
+ r += rx
+ return r
+
+
+ def readinput(self, dispatcher):
+ ostream = dispatcher.stdout
+ while True:
+ line = ostream.readline()
+ if not line:
+ break
+ id, line = line.rstrip().split(':', 1)
+ try:
+ if line[0] == '=':
+ self._stop_server()
+ # The dispatcher has just started, make it available
+ self.larch_dispatcher = dispatcher
+ # Reenable the gui, and queue the command
+ ui.idle_add(self._dispatcher_started, self.runcmd)
+ continue
+ elif line[0] != '/':
+ line = serialize.loads(line)
+ except:
+ line = '[[%s]]' % line
+ ui.idle_add(self.addline, line) # cross-thread call
+
+ if self.larch_dispatcher == None:
+ self._stop_server()
+ ui.idle_add(self._dispatcher_started, None)
+
+
+
+ def _sudo_wait(self):
+ ui.command(':larch.busy', [':larch'], True)
+
+
+ def _dispatcher_started(self, cmd):
+ if cmd:
+ cmd()
+ else:
+ ui.command('infoDialog',
+ ("%s:\n %s" % (ui.data('authfail'), self.geterr())),
+ 'sudo')
+ ui.command(':larch.busy', [':larch'], False)
+
+
+ def _stop_server(self):
+ # Stop the password socket-server
+ self.sserver.shutdown()
+ self.sserver = None
+
+
+ def close(self):
+ if self.larch_dispatcher:
+ self.istream.write('quit\n')
+ self.istream.flush()
+ self.larch_dispatcher.wait()
+
+
+ def interrupt(self):
+ logger.addLine('--Terminate--')
+ self.istream.write('kill\n')
+ self.istream.flush()
+
+
+ def addline(self, message):
+ """A line has been received from the script.
+ This must be handled in the main thread.
+ The input lines are filtered for pacman, mksquashfs and mkisofs
+ progress output so that appropriate progress reports can be given.
+ """
+ if 'pacman:' in message:
+ progress.set(message[2:])
+ if message.endswith('|100'):
+ progress.set()
+ message = message.rsplit('|', 1)[0].rstrip()
+ else:
+ return
+
+ if 'mksquashfs:' in message:
+ progress.set(message[2:])
+ self.progress_waiting = message
+ return
+
+ if 'mkisofs:' in message:
+ progress.set(message[2:])
+ self.progress_waiting = ">_mkisofs: completed"
+ return
+
+ if self.progress_waiting:
+ progress.set()
+ progress.addLine(self.progress_waiting)
+ self.progress_waiting = None
+
+ progress.addLine(message)
+ if message[0] == '/':
+ # process finished:
+ auto = False
+ if self.atend:
+ auto = self.atend(int(message[1:]))
+ progress.end(auto)
+ self.cmd = None
+ elif message.startswith('?>'):
+ # a query (yes/no)
+ # Pop up the query
+ reply = '??YES' if ui.command('confirmDialog',
+ message[2:], self.cmd) else '??NO'
+ self.istream.write(reply + '\n')
+ self.istream.flush()
+
+
+ def archin(self, cmd, installrepos):
+ args = ['-p', project_manager.profile_path,
+ '-i', project_manager.get_ipath()[1],
+ '-c', project_manager.get('pacman_cache')]
+ rf = project_manager.project_dir + '/pacman.conf.repos'
+ if installrepos and os.path.isfile(rf):
+ args += ['-r', rf]
+ self.call('archin', args + cmd.split())
+
+
+ def larchify(self, oldsyssqf, oldlocales):
+ args = ['-p', project_manager.profile_path,
+ '-i', project_manager.get_ipath()[1],]
+ if oldsyssqf:
+ args.append('-o')
+ if oldlocales:
+ args.append('-l')
+ self.call('larchify', args)
+
+
+ def testmedium(self, path, atend):
+ self.call('live_iso', ['-T', '-S', path,], atend=atend)
+
+
+ def writemedium(self, path, args, dest=None):
+ if dest == 'BOOTISO':
+ args.append(path)
+ cmd = 'boot_iso'
+ else:
+ if path:
+ args += ['-S', path]
+ else:
+ args += ['-p', project_manager.profile_path]
+ args += ['-i', project_manager.get_ipath()[1]]
+ if dest != None:
+ args.append(dest)
+ cmd = 'live_part'
+ else:
+ cmd = 'live_iso'
+ self.call(cmd, args)
+
+
+
+class MyHandler(SocketServer.StreamRequestHandler):
+ def handle(self):
+ self._event = threading.Event()
+ # self.rfile is a file-like object created by the handler;
+ # we can now use e.g. readline() instead of raw recv() calls
+ data = self.rfile.readline().strip()
+ if data == 'pw-get':
+ ui.idle_add(self.dialog)
+ self._event.wait()
+ # Likewise, self.wfile is a file-like object used to write back
+ # to the client
+ self.wfile.write(self.pw)
+
+ def dialog(self):
+ ok, self.pw = ui.command('textLineDialog',
+ ui.data('getpw'),
+ 'sudo', '', True)
+ if not ok:
+ self.pw = ''
+ self._event.set()
+
+
+
+fs = Fs()
+
+def filesystem(key, *args):
+ return exports[key](*args)
+
+__builtin__.filesystem = filesystem
+__builtin__.larchscripts = LarchScripts()
+
+import project
+project_manager.init()
+
diff --git a/build_tools/larch7/larch0/gui/dirview.py b/build_tools/larch7/larch0/gui/dirview.py
new file mode 100755
index 0000000..303f9b6
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/dirview.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+
+
+# Next look at switching to a selected directory from the path (DONE),
+# switching to a directory in the list, and removing/changing toolbar
+# buttons (and their actions).
+# Have a checkbutton for hidden files / directories somewhere.
+
+
+import os
+from PyQt4 import QtGui, QtCore
+
+def clicked(r, c):
+ print r, c
+
+def iclicked(item):
+ print item
+
+
+class DirListing(QtGui.QTreeWidget): #qt
+ # Only using top-level items of the tree
+ def __init__(self):
+ QtGui.QTreeWidget.__init__(self) #qt
+ self._hcompact = False # used for scheduling header-compaction
+ self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
+ self.headers(['Name']) #qt
+ self.setRootIsDecorated(False) #qt
+
+ self.connect(self, QtCore.SIGNAL('itemSelectionChanged()'),
+ self.s_select)
+ self.connect(self, QtCore.SIGNAL('itemClicked(QTreeWidgetItem *,int)'),
+ self.s_clicked)
+
+
+ def s_select(self):
+ # Signal a selection change, passing the new selection list (indexes)
+ s = [self.indexOfTopLevelItem(i) for i in self.selectedItems()] #qt
+ print "Sel", s
+
+
+ def s_clicked(self, item, col): #qt
+ # I guess I should use this for selection if using single
+ # click actions, because setting a list up might cause the
+ # first item to be selected (it doesn't, actually, so select
+ # could be used), and it should
+ # only change directory if actually clicked.
+
+
+ """This is intended for activating a user-defined editing function.
+ Tests showed that this is called after the selection is changed, so
+ if using this signal, use it only in 'Single' selection mode and
+ use this, not 'select' to record selection changes. Clicking on the
+ selected row should start editing the cell, otherwise just change
+ the selection.
+ """
+ ix = self.indexOfTopLevelItem(item) #qt
+ print ix, col
+
+
+
+ def headers(self, headers): #qt
+ self.setHeaderLabels(headers) #qt
+ if self._hcompact:
+ self._compact()
+
+ def set(self, items, index=-1): #qt
+ # Note that each item must be a tuple/list containing
+ # entries for each column.
+ self.clear() #qt
+ c = 0
+ for i in items:
+ item = QtGui.QTreeWidgetItem(self, i) #qt
+ self.addTopLevelItem(item) #qt
+ if c == index:
+ self.setCurrentItem(item)
+ c += 1
+ if self._hcompact:
+ self._compact()
+
+ def x__compact(self, on=True):
+ self._hcompact = on
+ if on:
+ self._compact()
+
+ def _compact(self):
+ for i in range(self.columnCount()): #qt
+ self.resizeColumnToContents(i) #qt
+
+
+
+def dirsel(action):
+ print action.xtag
+ i = 0
+ if action.xindex == 0:
+ print '/'
+ else:
+ path = ''
+ while i < action.xindex:
+ i += 1
+ path += '/' + dirs[i]
+ print path
+ setlisting(path)
+# the toolbuttons should stay the same until a different lower directory
+# is chosen (one not in the old list?)
+
+
+def setlisting(path):
+ dlist = os.listdir(path)
+ dldir = []
+ dlfile = []
+ for f in dlist:
+ if os.path.isdir(path + '/' + f):
+ dldir.append('d:' + f)
+ else:
+ dlfile.append('f:' + f)
+ dldir.sort()
+ dlfile.sort()
+ listing.set([d] for d in (dldir + dlfile))
+
+
+if __name__ == '__main__':
+
+ import sys
+
+ app = QtGui.QApplication(sys.argv)
+ app.setStyleSheet("""
+ QToolButton {
+ border: 2px solid #8f8f91;
+ border-radius: 6px;
+ background-color: yellow;
+ }
+
+ QToolButton:checked {
+ background-color: #f0c080;
+ }
+""")
+
+ window = QtGui.QWidget()
+ listing = DirListing()
+ bar = QtGui.QToolBar()
+ bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
+ actg = QtGui.QActionGroup(bar)
+ QtCore.QObject.connect(actg, QtCore.SIGNAL('triggered (QAction *)'), dirsel)
+ actg.setExclusive(True)
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(bar)
+ layout.addWidget(listing)
+ window.setLayout(layout)
+ window.resize(600, 480)
+
+
+
+ path = '/home/mt/DATA/pyjamas'
+
+
+ dirs = path.split('/')
+# dirs = ['', 'home', 'mt', 'DATA', 'software-verylong', 'DOCS', 'python_qt']
+ butix = 0
+ for but in dirs:
+ bw = bar.addAction(but+'/')
+ bw.setCheckable(True)
+ actw = actg.addAction(bw)
+ actw.xtag = but
+ actw.xindex = butix
+ butix += 1
+
+ setlisting(path)
+
+ window.show()
+
+ sys.exit(app.exec_())
diff --git a/build_tools/larch7/larch0/gui/dispatcher.py b/build_tools/larch7/larch0/gui/dispatcher.py
new file mode 100755
index 0000000..bcdc94c
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/dispatcher.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# dispatcher.py - Subprocess running as root to call larch scripts
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+#2010.07.19
+
+import os, sys, threading, signal
+from subprocess import Popen, PIPE, STDOUT
+try:
+ import json as serialize
+except:
+ import simplejson as serialize
+
+module_dir = os.path.dirname(os.path.realpath(__file__))
+base_dir = os.path.dirname(module_dir)
+
+
+def out(key, line):
+ sys.stdout.write(str(key) + ':' + line + '\n')
+ sys.stdout.flush()
+
+# Signal dispatcher started
+out(0, '==')
+
+def input_reader():
+ global process
+ id = process.pid
+ while True:
+ line = process.stdout.readline()
+ if not line:
+ break
+
+ out(id, line.rstrip())
+
+ process.wait()
+ out(id, '/%d' % process.returncode)
+ process = None
+
+def kill():
+ """Kill subprocesses (well, interrupt actually).
+ """
+ if process:
+ os.killpg(process.pid, signal.SIGINT)
+
+
+process = None
+while True:
+ line = sys.stdin.readline()
+ if line:
+ line = line.rstrip()
+ else:
+ # Controlling process died
+ break
+ cmdx = line.split(None, 1)
+ cmd = cmdx[0]
+ response = ''
+ if cmd in ('archin', 'larchify', 'live_iso', 'live_part', 'boot_iso',
+ 'test'):
+ # Start a larch script, together with a thread to read from it.
+ # The arguments are passed as a serialized list
+ if process:
+ response = ("!*** Bug (Dispatcher ERROR):"
+ " process already running (%d)" % process.pid)
+ else:
+ cmdx2 = cmdx[1].split(':', 1)
+ cmds = ([base_dir + '/cli/%s.py' % cmd, '-s']
+ + serialize.loads(cmdx2[1]))
+ process = Popen(cmds,
+ preexec_fn=os.setpgrp, # to facilitate interruption
+ cwd=cmdx2[0], # set to project directory
+ stdin=PIPE,
+ stdout=PIPE,
+ stderr=STDOUT)
+ t = threading.Thread(target=input_reader, args=())
+ t.start()
+
+ elif cmd.startswith('??'):
+ process.stdin.write(cmd + '\n')
+ process.stdin.flush()
+ elif cmd == 'kill':
+ kill()
+ elif cmd != 'quit':
+ response = "!*** Bug (Dispatcher command): " + line
+ sys.stderr.write(response + '\n!+\n')
+ sys.stderr.flush()
+ if cmd == 'quit':
+ break
+
+kill()
diff --git a/build_tools/larch7/larch0/gui/front/docviewer.py b/build_tools/larch7/larch0/gui/front/docviewer.py
new file mode 100644
index 0000000..a0af083
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/docviewer.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+#
+# docviewer.py
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.06.24
+
+
+import os
+
+
+class DocViewer:
+ def __init__(self):
+ self.index = self._getPage('index.html')
+ self.homepath = None
+ ui.widgetlist(fss('fetch_layout', 'docviewer.uim'))
+
+ ui.connectlist(
+ ('doc:hide*clicked', self._hide),
+ ('doc:back*clicked', self._back),
+ ('doc:forward*clicked', self._forward),
+ ('doc:home*clicked', self.gohome),
+ ('doc:parent*clicked', self.goto),
+ (':docs*clicked', self._show),
+ )
+
+ def _show(self):
+ ui.runningtab(3)
+
+ def _hide(self):
+ ui.runningtab()
+
+ def _back(self):
+ ui.command('doc:content.prev')
+
+ def _forward(self):
+ ui.command('doc:content.next')
+
+ def _getPage(self, page):
+ return fss('get_docs_url', page)
+
+ def gohome(self, home=None):
+ if home:
+ self.homepath = self._getPage(home)
+ self.goto(self.homepath)
+
+ def goto(self, path=None):
+ if not path:
+ path = self.index
+ ui.command('doc:content.setUrl', path)
diff --git a/build_tools/larch7/larch0/gui/front/editor.py b/build_tools/larch7/larch0/gui/front/editor.py
new file mode 100644
index 0000000..649f55c
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/editor.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# editor.py
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.06.24
+
+class Editor:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'editor.uim'))
+ ui.connectlist(
+ ('edit:ok*clicked', self.ok),
+ ('edit:cancel*clicked', self.cancel),
+ ('edit:revert*clicked', self.dorevert),
+ ('edit:copy*clicked', self.copy),
+ ('edit:cut*clicked', self.cut),
+ ('edit:paste*clicked', self.paste),
+ ('edit:undo*clicked', self.undo),
+ ('edit:redo*clicked', self.redo),
+ )
+
+ def start(self, title, endcall, text='', revert=None):
+ ui.command('edit:title.markup', ['h3', title])
+ self.endcall = endcall
+ self.revert = revert
+ try:
+ self.text0 = revert() if text == None else text
+ except:
+ run_error("BUG: Editor - no revert function?")
+ ui.command('edit:content.text', self.text0)
+ ui.runningtab(4)
+
+ def ok(self):
+ self.endcall(ui.command('edit:content.get'))
+ ui.runningtab()
+
+ def cancel(self):
+ ui.runningtab()
+
+ def dorevert(self):
+ if self.revert:
+ self.text0 = self.revert()
+ ui.command('edit:content.text', self.text0)
+
+ def copy(self):
+ ui.command('edit:content.copy')
+
+ def cut(self):
+ ui.command('edit:content.cut')
+
+ def paste(self):
+ ui.command('edit:content.paste')
+
+ def undo(self):
+ ui.command('edit:content.undo')
+
+ def redo(self):
+ ui.command('edit:content.redo')
+
+ def edit(self, fname, source=None, label=None, filter=None):
+ """Files (<fname> and <source>) can be either an absolute path or else
+ relative to the profile directory, the application base directory
+ or the working directory. Relative paths are determined by the
+ prefixes 'profile:', 'base:' or 'working:'.
+ If the file <fname> already exists its contents will be taken as the
+ starting point, otherwise the file <source>, which may also be an
+ empty string, will be read in.
+ Whichever file is available its contents can be filtered by an
+ optional 'filter' function, which takes the file contents as a
+ string as argument and returns the transformed contents as another
+ string.
+ """
+ def revert():
+ """If a file is addressed by 'source' revert to its contents,
+ if source is "", clear the contents, otherwise revert to the
+ contents as they were before entering the editor.
+ """
+ return textsrc if source != None else text0
+
+ def endfile(text):
+ t = text.encode("utf8")
+ if t and (t[-1] != "\n"):
+ t += "\n"
+ fss('savefile', fname, text)
+
+ if source != None:
+ textsrc = "" if source == "" else fss('readfile', source,
+ filter=filter)
+ # Read the file, if it exists, else return None
+ text0 = fss('readfile', fname, filter=filter, trap=False)
+ if text0 == None:
+ assert source != None # The file must be present
+ text0 = textsrc
+ if not label:
+ label = ui.command('editor_data.get', 'msg_dflt') % fname
+ self.start(label, endfile, text0, revert)
+
diff --git a/build_tools/larch7/larch0/gui/front/logview.py b/build_tools/larch7/larch0/gui/front/logview.py
new file mode 100644
index 0000000..b9d2ac3
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/logview.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+#
+# logview.py
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.09
+
+import locale
+# Try to work around problems when the system encoding is not utf8
+encoding = locale.getdefaultlocale()[1]
+if encoding == "UTF8":
+ encoding = None
+
+#TODO: progress bar?
+class Progress:
+ def __init__(self):
+ self.active = False
+ ui.widgetlist(fss('fetch_layout', 'progress.uim'))
+ ui.connect('progress:done*clicked', self._done)
+
+ def _done(self):
+ self.active = False
+ ui.runningtab(0)
+
+ def start(self):
+ # Set busy cursor on the progress area
+ ui.command('progress:page.busycursor', True)
+ # Initialize widgets
+ ui.command("progress:text.text")
+ ui.command("progress:progress.text")
+ ui.command("progress:done.enable", False)
+ ui.command("progress:cancel.enable", True)
+ self.active = True
+ ui.runningtab(1)
+
+ def end(self, auto=False):
+ ui.command("progress:cancel.enable", False)
+ ui.command("progress:done.enable", True)
+ # Clear busy cursor on the progress area
+ ui.command('progress:page.busycursor', True)
+ ui.command('progress:page.busycursor', False)
+ if auto:
+ self._done()
+
+ def addLine(self, line):
+ # Try to work around problems when the system encoding is not utf8
+ if encoding:
+ line = line.decode(self.encoding, "replace").encode("UTF8")
+ ui.command("progress:text.append_and_scroll", line)
+ logger.addLine(line)
+
+ def set(self, text=""):
+ ui.command("progress:progress.text", text)
+
+
+class Logger:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'logger.uim'))
+ ui.connectlist(
+ ('log:clear*clicked', self.clear),
+ ('log:hide*clicked', self._hide),
+ (':showlog*clicked', self._show),
+ )
+
+ def clear(self):
+ ui.command('log:text.text')
+
+ def addLine(self, line):
+ # Try to work around problems when the system encoding is not utf8
+ ui.command('log:text.append_and_scroll', line)
+
+ def _show(self):
+ ui.runningtab(2)
+
+ def _hide(self):
+ ui.runningtab()
diff --git a/build_tools/larch7/larch0/gui/front/mainwindow.py b/build_tools/larch7/larch0/gui/front/mainwindow.py
new file mode 100644
index 0000000..407b46a
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/mainwindow.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+#2010.07.13
+
+import __builtin__
+from uim import Uim, debug
+__builtin__.debug = debug
+
+
+_running = False
+def fss(*args, **kargs):
+ while True:
+ if _running:
+ ui.command(':larch.busy', [], True)
+ ok, result = filesystem(*args)
+ if _running:
+ ui.command(':larch.busy', [], False)
+ if ok:
+ filter = kargs.get('filter')
+ return filter(result) if filter else result
+
+ if kargs.get('trap', True):
+ ui.command('errorDialog', result)
+ # That might return, but the gui should quit (soon?).
+ return None
+
+__builtin__.fss = fss
+
+
+
+class Ui(Uim):
+ def __init__(self):
+ Uim.__init__(self)
+
+ def runningtab(self, i=None):
+ if (i == None):
+ i = 1 if progress.active else 0
+ if (i == 0) and hasattr(stage, 'reenter'):
+ stage.reenter()
+ self.command(':tabs.set', i)
+
+ def fileDialog(self, message, startdir=None, create=False,
+ dirsonly=False, file=None, bookmarks=None, filter=None):
+ return self.command('fileDialog', message, startdir, None,
+ dirsonly, create, file, bookmarks, filter)
+
+ def enable_installation_page(self, on):
+ self.command(':notebook.enableTab', 1, on)
+
+ def quit(self):
+ """Do any tidying up which may be necessary.
+ """
+ larchscripts.close()
+ Uim.quit()
+
+ def data(self, key):
+ return self.command('main_page_data.get', key)
+
+__builtin__.ui = Ui()
+
+
+def tab_changed(index):
+ __builtin__.stage = pages[index]
+ stage.enter()
+ui.connect(':notebook*changed', tab_changed)
+
+
+from page_project import ProjectSettings
+from page_installation import Installation
+from page_larchify import Larchify
+from page_mediumprofile import MediumProfile
+from page_medium import Medium
+
+
+from docviewer import DocViewer
+
+from editor import Editor
+
+from logview import Logger, Progress
+
+
+def run_error(message, title=None):
+ ui.command('warningDialog', message, title)
+__builtin__.run_error = run_error
+
+pages = []
+def start():
+ pages.append(ProjectSettings())
+ pages.append(Installation())
+ pages.append(Larchify())
+ pages.append(MediumProfile())
+ pages.append(Medium())
+
+ __builtin__.docviewer = DocViewer()
+ __builtin__.edit = Editor().edit
+
+ __builtin__.progress = Progress()
+ __builtin__.logger = Logger()
+
+ MainWindow = fss('fetch_layout', 'page_main.uim')
+ ui.widgetlist(MainWindow)
+
+ ui.connect('$$$uiclose$$$', ui.quit)
+ ui.connect('$$$uiquit$$$', ui.quit)
+ ui.connect('$$$cancel$$$', larchscripts.interrupt)
+
+ ui.command(':larch.pack')
+ # Set up the first gui page (project settings)
+ pages[0].setup()
+ ui.command(':larch.show')
+ _running = True
+ ui.run()
+
diff --git a/build_tools/larch7/larch0/gui/front/page_installation.py b/build_tools/larch7/larch0/gui/front/page_installation.py
new file mode 100644
index 0000000..111e247
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/page_installation.py
@@ -0,0 +1,188 @@
+# page_installation.py - Handler for the installation page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.19
+
+class Installation:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'page_installation.uim'))
+
+ ui.connectlist(
+ (':addedpacks*clicked', self.edit_addedpacks),
+ (':vetopacks*clicked', self.edit_vetopacks),
+ (':pacmanconf*clicked', self.edit_pacmanconf),
+ (':repos*clicked', self.edit_repos),
+ (':editmirrorlist*clicked', self.edit_mirrorlist),
+ (':cache_change*clicked', self.change_cache),
+ (':editrepolist*clicked', self.edit_repolist),
+ (':install*clicked', self.install),
+ (':sync*clicked', self.dosync),
+ (':update*clicked', self.doupdate),
+ (':add*clicked', self.doadd),
+ (':remove*clicked', self.doremove),
+ (':installrepos*toggled', self.installrepo),
+ )
+
+
+ def enter(self):
+ """This is called when the page is entered/selected/shown.
+ It performs initializations which depend on the state.
+ """
+ docviewer.gohome('gui_installation.html')
+ # Set package cache display
+ ui.command(':cache_show.text', fss('getitem', 'pacman_cache'))
+ ui.command(':installrepos.opton', fss('getbool', 'installrepo'))
+
+
+ def data(self, key):
+ return ui.command('install_page_data.get', key)
+
+
+ def edit_addedpacks(self):
+ edit('profile:addedpacks') # In profile dir
+
+
+ def edit_vetopacks(self):
+ # If there is no vetopacks file, start an empty one
+ edit('profile:vetopacks', "") # In profile dir
+
+
+ def edit_pacmanconf(self):
+ edit('profile:pacman.conf.options', # In profile dir
+ 'base:data/pacman.conf', # Relative to base_dir
+ label=self.data('edit_pc'),
+ filter=pacmanoptions)
+
+
+ def edit_repos(self):
+ """This edits the repository list file for the live system.
+ It will be used to construct the /etc/pacman.conf file.
+ If the option to specify a different file for the installation
+ stage is not enabled (the default), this file will also be used
+ for the installation.
+ """
+ edit('profile:pacman.conf.repos', # In profile dir
+ 'base:data/pacman.conf.repos', # Relative to base_dir
+ label=self.data('edit_pr'))
+
+
+ def edit_mirrorlist(self):
+ ml = '/etc/pacman.d/mirrorlist'
+ if not fss('isfile', ml):
+ ml = 'base:data/mirrorlist' # Relative to base_dir
+ edit('working:mirrorlist', ml,
+ label=self.data('edit_mli'))
+
+
+ def change_cache(self):
+ # Is anything more necessary? Do I need to test the path?
+ # Would a directory browser be better?
+ ok, path = ui.command('textLineDialog',
+ self.data('prompt_ncp'),
+ None, fss('getitem', 'pacman_cache'))
+ if ok:
+ self.set_pacman_cache(path)
+
+
+ def set_pacman_cache(self, path):
+ path = path.strip().rstrip('/')
+ fss('setitem', 'pacman_cache', path)
+ ui.command(':cache_show.text', path)
+
+
+ def edit_repolist(self):
+ """This edits the repository list file used for installation,
+ if the corresponding option is enabled.
+ """
+ # Should it be based on the default or on the profile?
+ rf = 'profile:pacman.conf.repos'
+ if not fss('isfile', rf):
+ rf = 'base:data/pacman.conf.repos' # Relative to base_dir
+ edit('working:pacman.conf.repos', rf,
+ label=self.data('edit_pri'))
+
+
+ def installrepo(self, on):
+ fss('setbool', 'installrepo', on)
+
+
+ def install(self):
+ """Start the installation.
+ """
+ self.archin('install')
+
+
+ def dosync(self):
+ self.archin('refresh')
+
+
+ def doupdate(self):
+ f = ui.fileDialog(message=self.data('msg_pu'),
+ filter=(self.data('filter_pu'), '*.pkg.tar.*'))
+ if f:
+ self.archin('update ' + f)
+
+
+ def doadd(self):
+ ok, plist = ui.command('textLineDialog',
+ self.data('prompt_pi'),
+ 'pacman -S')
+ if ok:
+ self.archin('sync ' + plist.strip())
+
+
+ def doremove(self):
+ ok, plist = ui.command('textLineDialog',
+ self.data('prompt_pr'),
+ 'pacman -Rs')
+ if ok:
+ self.archin('remove ' + plist.strip())
+
+
+ def archin(self, cmd):
+ """This runs the 'archin' script (as root). It doesn't wait for
+ completion because the output must be collected and displayed
+ while it is running. The display switches the view to the
+ progress reporting page. It should probably activate the busy
+ cursor too.
+ """
+ larchscripts.archin(cmd, ui.command(':installrepos.active'))
+
+
+
+def pacmanoptions(text):
+ """A filter for pacman.conf to remove the repository info.
+ """
+ texto = ""
+ block = ""
+ for line in text.splitlines():
+ block += line + "\n"
+ if line.startswith("#["):
+ break
+ if line.startswith("[") and not line.startswith("[options]"):
+ break
+ if not line.strip():
+ texto += block
+ block = ""
+ return texto
+
+
+
diff --git a/build_tools/larch7/larch0/gui/front/page_larchify.py b/build_tools/larch7/larch0/gui/front/page_larchify.py
new file mode 100644
index 0000000..9868727
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/page_larchify.py
@@ -0,0 +1,248 @@
+# page_larchify.py - Handler for the project settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.13
+
+USERINFO = ['pw', 'maingroup', 'uid', 'skel', 'xgroups', 'expert']
+
+class Larchify:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'page_larchify.uim'))
+
+ ui.connectlist(
+ (':build*clicked', self.build),
+ (':ssh*toggled', self.sshtoggle),
+ (':locales*clicked', self.locales),
+ (':rcconf*clicked', self.rcconf),
+ (':initcpio*clicked', self.initcpio),
+ (':overlay*clicked', self.overlay),
+ (':utable*clicked', self.uedit),
+ (':useradd*clicked', self.useradd),
+ (':userdel*clicked', self.userdel),
+ (':rootpwb*clicked', self.rootpw),
+ )
+
+ self.userheaders = self.data('uheaders')
+
+
+ def data(self, key):
+ return ui.command('larchify_page_data.get', key)
+
+
+ def enter(self):
+ """This is called when the page is entered/selected/shown.
+ It performs initializations which depend on the state.
+ """
+ docviewer.gohome('gui_larchify.html')
+
+ # Check that it could possibly be an Arch installation
+ idir = fss('get_installation_dir')
+ couldBeArch = fss('isdir', ':var/lib/pacman/local')
+ ui.command(':build.enable', couldBeArch)
+ if not couldBeArch:
+ run_error(_("No Arch installation at %s") % idir)
+
+ # ssh keys
+ sshon = fss('isfile', ':usr/bin/ssh-keygen')
+ ui.command(':ssh.enable', sshon)
+
+ if fss('isfile', 'profile:nosshkeys'):
+ sshon = False
+ ui.command(":ssh.set", sshon)
+
+ # users table
+ idir_normal = idir != '/'
+ ui.command(':users.enable', idir_normal)
+ if idir_normal:
+ # Fetch users information
+ fss('newUserinfo')
+ self.readuserinfo()
+
+ # Root password
+ self.showrootpw()
+
+ self.reenter()
+
+
+ def reenter(self):
+ """These also need resetting after a build run.
+ """
+ # Whether there is an old system.sqf to reuse?
+ ossqf = fss('oldsqf')
+ if not ossqf:
+ ui.command(':oldsquash.set', False)
+ ui.command(':oldsquash.enable', ossqf)
+
+ # Whether there is a set of old glibc locales to reuse?
+ olcl = fss('oldlocales')
+ if not olcl:
+ ui.command(':oldlocales.set', False)
+ ui.command(':oldlocales.enable', olcl)
+
+#TODO: Remove hack if the underlying bug gets fixed
+ ui.command(":larchify_advanced.enable_hack")
+
+
+ def readuserinfo(self, select=None):
+ """'select' should be a username, defaulting to the first entry.
+ """
+ self.usersel = 0
+ self.userlist = []
+ i = 0
+ for u in fss('allusers'):
+ self.userlist.append(self.userinfolist(u))
+ if u == select:
+ self.usersel = i
+ i += 1
+ ui.command(':utable.set', self.userlist, self.usersel)
+
+
+ def userinfolist(self, user):
+ return [user] + fss('getuserinfo', user, USERINFO)
+
+
+ def uedit(self, row, column):
+ if self.usersel == row:
+ uname = self.userlist[row][0]
+ ulcell = self.userlist[row][column]
+ if column == 4:
+ ok, text = self.select_skel(ulcell)
+ else:
+ ok, text = ui.command('textLineDialog',
+ self.userheaders[column] + ':', 'larchify', ulcell)
+ text = text.strip()
+ if ok:
+ try:
+ if (column == 0) and (text != ''):
+ # Rename the user, by adding a new one and deleting
+ # the old
+ uname = text
+ fss('newuser', uname)
+ i = 0
+ for f in USERINFO:
+ i += 1
+ fss('userset', uname, f, self.userlist[row][i])
+ if not fss('deluser', ulcell):
+ run_error(self.data('rn_error'))
+
+ else:
+ fss('userset', uname, USERINFO[column-1], text)
+ fss('saveusers')
+
+ except:
+ run_error(self.data('ud_error'))
+ self.readuserinfo(uname)
+
+ else:
+ self.usersel = row
+
+
+ def select_skel(self, current):
+ # Present a list of available 'skel' folders
+ self.skellist = [self.data('def_skel')]
+ for f in fss('listskels'):
+ self.skellist.append(f.rsplit('/skel_', 1)[1])
+ try:
+ i = self.skellist.index(current)
+ except:
+ i = 0
+ ok, skeli = ui.command('listDialog', self.data('skel_lbl'),
+ self.data('skel_ttl'), self.skellist, i)
+ if ok:
+ return (True, '' if skeli == self.skellist[0]
+ else skeli.split()[0])
+ return (False, '')
+
+
+ def useradd(self):
+ ok, name = ui.command('textLineDialog', self.data('newlogin'))
+ if ok:
+ name = name.strip()
+ if name != '' and fss('newuser', name):
+ self.userlist.append(self.userinfolist(name))
+ self.usersel = len(self.userlist) -1
+ ui.command(':utable.set', self.userlist, self.usersel)
+
+
+ def userdel(self):
+ if self.usersel >= 0:
+ user = self.userlist[self.usersel][0]
+ if fss('deluser', user):
+ del(self.userlist[self.usersel])
+ lu = len(self.userlist)
+ if lu:
+ if lu <= self.usersel:
+ self.usersel -= 1
+ ui.command(':utable.set', self.userlist, self.usersel)
+
+
+ def showrootpw(self):
+ self.rootpw = fss('readfile', 'profile:rootpw', trap=False)
+ if self.rootpw == None:
+ self.rootpw = ""
+ ui.command(':rootpwe.text', self.rootpw)
+
+
+ def rootpw(self):
+ ok, pw = ui.command('textLineDialog', self.data('newrootpw'),
+ "larchify", self.rootpw)
+ if ok:
+ pw = pw.strip()
+ if pw:
+ fss('savefile', 'profile:rootpw', pw)
+ else:
+ fss('rm_rf', 'profile:rootpw')
+ self.showrootpw()
+
+
+ def sshtoggle(self, on):
+ """Whether the system ssh keys are pregenerated
+ depends on the presence of the profile file 'nosshkeys'
+ (and of course on openssh being installed).
+ """
+ sshoff = fss('isfile', 'profile:nosshkeys')
+ if on:
+ if sshoff:
+ fss('rm_rf', 'profile:nosshkeys')
+ elif not sshoff:
+ fss('savefile', 'profile:nosshkeys', "Don't pregenerate ssh keys")
+
+
+ def locales(self):
+ edit('profile:rootoverlay/etc/locale.gen', 'install:etc/locale.gen')
+
+
+ def rcconf(self):
+ edit('profile:rootoverlay/etc/rc.conf', 'install:etc/rc.conf')
+
+
+ def initcpio(self):
+ edit('profile:rootoverlay/etc/mkinitcpio.conf.larch0',
+ 'install:etc/mkinitcpio.conf.larch0')
+
+
+ def overlay(self):
+ fss('browse', 'profile:rootoverlay')
+
+
+ def build(self):
+ larchscripts.larchify(ui.command(':oldsquash.active'),
+ ui.command(':oldlocales.active'))
diff --git a/build_tools/larch7/larch0/gui/front/page_medium.py b/build_tools/larch7/larch0/gui/front/page_medium.py
new file mode 100644
index 0000000..75f0efe
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/page_medium.py
@@ -0,0 +1,320 @@
+# page_medium.py - Handler for the project settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.13
+
+"""This page can also handle sources other than the normal larch
+installation. It should convert any larch image into
+another, with different medium and/or different bootloader, so long as the
+bootloader is supported by the source image.
+"""
+
+import os
+BOOTLOADERS = ('grub', 'syslinux')
+
+class Medium:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'page_medium.uim'))
+
+ ui.connectlist(
+ (':source_larch*toggled', self.src_larch),
+ (':source_dev*toggled', self.src_dev),
+ (':source_iso*toggled', self.src_iso),
+ (':source_path*toggled', self.src_path),
+ (':source_select*clicked', self.src_select),
+ (':vlabelb*clicked', self.newlabel),
+ (':grub*toggled', self.grub),
+ (':syslinux*toggled', self.syslinux),
+ (':selectpart*clicked', self.choosepartition),
+ (':search*toggled', self.searchtoggled),
+ (':destination*toggled', self.activate_actions),
+ (':make_medium*clicked', self.make),
+ (':bootcd*clicked', self.makeboot),
+ )
+ ui.command(':bootcd.enable', False)
+
+
+ def enter(self):
+ """This is called when the page is entered/selected/shown.
+ It performs initializations which depend on the state.
+ """
+ self.source = ''
+ self.destination = ''
+ ui.command(':larchpart.text')
+ docviewer.gohome('gui_medium.html')
+ if ui.command(':source_larch.active'):
+ self.src_larch(True)
+ else:
+ ui.command(':source_larch.set', True)
+ ui.command(':%s.set' % fss('getitem', 'medium_search'), True)
+ ui.command(':vlabele.text', fss('get_mediumlabel'))
+
+
+ def data(self, key):
+ return ui.command('medium_page_data.get', key)
+
+
+ def setupbootloader(self, init=False):
+ if init:
+ self.bootloaders = []
+ self.nosave = False
+ self.source = ''
+ for b in BOOTLOADERS:
+ ui.command(':%s.set' % b, False)
+ ui.command(':%s.enable' % b, False)
+ pbl = fss('getitem', 'medium_btldr')
+ for b in self.bootloaders:
+ ui.command(':%s.enable' % b, True)
+ if self.bootloaders:
+ if pbl not in self.bootloaders:
+ pbl = self.bootloaders[0]
+ ui.command(':%s.set' % pbl, True)
+ ui.command(':dosave.set', not self.nosave)
+ ui.command(':source_show.text', self.source)
+ self.activate_actions()
+
+
+ def activate_actions(self, *args):
+ # There needs to be a bootloader
+ bl = self.get_bootloader()
+ bcdok = (ui.command(':source_dev.active')
+ and bool(self.source)
+ and bool(bl))
+ ui.command(':bootcd.enable', bcdok)
+ # If using a destination partition, that needs to be available.
+ wp = ui.command(':destination.active')
+ if wp:
+ # If writing to a partition without installing a bootloader
+ if ui.command(':nombr.active'):
+ bl = True # bootloader not necessary
+ dp = bool(self.destination)
+ else:
+ dp = True
+ ui.command(':make_medium.enable', bool(self.source) and bl and dp)
+ ui.command(':detection.enable', bcdok or wp)
+
+
+ def get_bootloader(self):
+ if ui.command(':grub.active'):
+ return 'grub'
+ if ui.command(':syslinux.active'):
+ return 'syslinux'
+ return None
+
+
+ def _testmedium(self, cc):
+ # Runs in background thread ...
+ self.bootloaders = []
+ if not (cc & 8):
+ self.bootloaders.append('syslinux')
+ if not (cc & 16):
+ self.bootloaders.append('grub')
+ self.nosave = bool(cc & 2)
+ ui.idle_add(self.setupbootloader, bool(cc & 1))
+ return True # return immediately to normal view
+
+
+ def src_larch(self, on):
+ if on:
+ mpath, ok, self.bootloaders, self.nosave = fss('testmedium')
+ if ok:
+ self.source = mpath
+ else:
+ self.source = ''
+ run_error(self.data('msg_med') % mpath)
+ self.setupbootloader()
+
+ ui.command(':source_select.enable', not on)
+ ui.command(':chroot.set', on)
+
+
+ def src_dev(self, on):
+ """The source system is on a mountable device, which must be
+ selected from a list of available devices (via the Choose button)
+ and tested for validity, maybe the presence of larch/system.sqf.
+ """
+ if on:
+ self.setupbootloader(init=True)
+
+
+ def src_iso(self, on):
+ """The source system is in an 'iso' file, which must be
+ selected by browsing the file system (via the Choose button)
+ and tested for validity, maybe the presence of larch/system.sqf.
+ """
+ if on:
+ self.setupbootloader(init=True)
+
+
+ def src_path(self, on):
+ """The source system is in a directory, which must be
+ selected by browsing the file system (via the Choose button)
+ and tested for validity, maybe the presence of larch/system.sqf.
+ """
+ if on:
+ self.setupbootloader(init=True)
+
+
+ def src_select(self):
+ """The function of this button varies according to the selected
+ source ...
+ """
+ src = None
+ if ui.command(':source_dev.active'):
+ part = self.selectpart()
+ if part:
+ src = part
+
+ elif ui.command(':source_iso.active'):
+ iso = ui.fileDialog(self.data('iso_src'),
+ filter=(self.data('iso_type'), '*.iso'))
+ if iso:
+ src = iso
+
+ elif ui.command(':source_path.active'):
+ medium = ui.fileDialog(self.data('medium_src'), dirsonly=True)
+ if medium:
+ src = medium
+
+ if src:
+ self.source = src
+ larchscripts.testmedium(src, self._testmedium)
+
+
+ def grub(self, on):
+ if on:
+ fss('setitem', 'medium_btldr', 'grub')
+
+ def syslinux(self, on):
+ if on:
+ fss('setitem', 'medium_btldr', 'syslinux')
+
+
+ def newlabel(self):
+ ok, l = ui.command('textLineDialog',
+ self.data('prompt_label'),
+ None, fss('getitem', 'medium_label'))
+ if ok:
+ ui.command(':vlabele.text', fss('set_mediumlabel', l))
+
+
+ def choosepartition(self):
+ p = self.selectpart(True)
+ if p:
+ ui.command(':larchpart.text', p)
+ self.destination = p
+ self.activate_actions()
+
+
+ def selectpart(self, write=False):
+ # Present a list of available partitions (only unmounted ones
+ # are included)
+ self.partlist = fss('get_partitions')
+ ok, choice = ui.command('listDialog',
+ self.data('parts_dst') if write else self.data('parts_src'),
+ self.data('parts_t'),
+ self.partlist, len(self.partlist) - 1)
+ # The partition to be used is fetched from the gui, so there is no
+ # need to save it anywhere else.
+ if ok:
+ return choice.split()[0]
+ else:
+ return None
+
+
+ def searchtoggled(self, on):
+ ui.command(':nolarchboot.enable', not on)
+
+
+ def make(self):
+ """Write the larch medium.
+ """
+ args = ['-l', ui.command(':vlabele.get')]
+ if ui.command(':syslinux.active'):
+ args.append('-b')
+
+ # Is it standard (using profile, etc.) or copying?
+ if ui.command(':source_larch.active'):
+ # Normal larch build
+ path = ''
+ if not ui.command(':chroot.active'):
+ args.append('-c')
+
+ else:
+ # Copying from another larch medium
+ path = self.source
+ if ui.command(':chroot.active'):
+ args.append('-C')
+
+ # Write to iso file or to partition?
+ if ui.command(':destination.active'):
+ # Write to partition
+ for db in ('label', 'uuid', 'device', 'search'):
+ if ui.command(':%s.active' % db):
+ detect = db
+ args += ['-d', detect]
+ if (detect != 'search') and ui.command(':nolarchboot.active'):
+ args.append('-n')
+ args.append('-a' if ui.command(':dosave.active') else '-A')
+ if ui.command(':noformat.active'):
+ args.append('-x')
+ if ui.command(':nombr.active'):
+ args.append('-m')
+ larchscripts.writemedium(path, args,
+ os.path.basename(self.destination))
+
+ else:
+ # Write an 'iso' file
+ df = self.isopath()
+ if df:
+ args += ['-D', df[0], '-o', df[1]]
+ larchscripts.writemedium(path, args)
+
+
+ def makeboot(self):
+ return
+ args = ['-l', fss('getbootisolabel')]
+ if ui.command(':syslinux.active'):
+ args.append('-b')
+ path = os.path.basename(self.source)
+ if ui.command(':chroot.active'):
+ args.append('-C')
+ df = self.isopath(bootiso=True)
+ if df:
+ args += ['-D', df[0], '-o', df[1]]
+ larchscripts.writemedium(path, args, 'BOOTISO')
+
+
+ def isopath(self, bootiso=False):
+ sdir = fss('getisosavedir')
+ ifname = fss('getbootisofile' if bootiso else 'getisofile')
+ path = ui.fileDialog(self.data('isopath'), startdir=sdir,
+ create=True, file=ifname, filter=(self.data('iso_type'), '*.iso'))
+ if path:
+ f = os.path.basename(path)
+ d = os.path.dirname(path)
+ if d != sdir:
+ fss('setitem', 'isosavedir', d)
+ if f != ifname:
+ fss('setitem', 'bootisofile' if bootiso else 'isofile', f)
+ return (d, f)
+
+ return None
diff --git a/build_tools/larch7/larch0/gui/front/page_mediumprofile.py b/build_tools/larch7/larch0/gui/front/page_mediumprofile.py
new file mode 100644
index 0000000..0ebb769
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/page_mediumprofile.py
@@ -0,0 +1,87 @@
+# page_mediumprofile.py - Handler for the project settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.14
+
+
+class MediumProfile:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'page_mediumprofile.uim'))
+
+ ui.connectlist(
+ (':bootlines*clicked', self.editbootlines),
+ (':grubtemplate*clicked', self.editgrub),
+ (':syslinuxtemplate*clicked', self.editsyslin),
+ (':cdroot*clicked', self.browsecdroot),
+ (':nosessionsave*toggled', self.nosessionsave),
+ )
+
+
+ def enter(self):
+ """This is called when the page is entered/selected/shown.
+ It performs initializations which depend on the state.
+ """
+ docviewer.gohome('gui_mediumprofile.html')
+ ui.command(':nosessionsave.set', fss('isfile', 'profile:nosave'))
+
+
+# def data(self, key):
+# return ui.command('mediumprofile_page_data.get', key)
+
+
+ def editbootlines(self):
+ edit('profile:bootlines', 'base:data/bootlines')
+
+
+ def editgrub(self):
+ f0 = 'profile:cd-root/grub0/menu.lst'
+ if not fss('isfile', f0):
+ f0 = 'base:cd-root/grub0/menu.lst'
+ edit('profile:cd-root/grub/menu.lst', f0)
+
+
+ def editsyslin(self):
+ f0 = 'profile:cd-root/isolinux0/isolinux.cfg'
+ if not fss('isfile', f0):
+ f0 = 'base:cd-root/isolinux0/isolinux.cfg'
+ edit('profile:cd-root/isolinux/isolinux.cfg', f0)
+
+
+ def browsecdroot(self):
+ fss('browse', 'profile:cd-root')
+
+
+ def nosessionsave(self, on):
+ """Whether session saving is available is firstly determined by
+ the writability of the boot device (assuming the extension
+ feature to allow the use of other devices is not being used).
+ The standard scripts will also not offer session saving if the
+ file larch/nosave is present on the boot medium.
+ """
+ ns = fss('isfile', 'profile:nosave')
+ if on:
+ if not ns:
+ fss('savefile', 'profile:nosave',
+ "Suggestion to disable session saving"
+ " (can be overridden)")
+ else:
+ fss('rm_rf', 'profile:nosave')
+
diff --git a/build_tools/larch7/larch0/gui/front/page_project.py b/build_tools/larch7/larch0/gui/front/page_project.py
new file mode 100644
index 0000000..e9b902d
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/page_project.py
@@ -0,0 +1,203 @@
+# page_project.py - Handler for the project settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.13
+
+import os
+
+class ProjectSettings:
+ def __init__(self):
+ ui.widgetlist(fss('fetch_layout', 'page_project.uim'))
+
+ ui.connectlist(
+ (':choose_profile_combo*changed', self.switch_profile),
+ (':profile_rename*clicked', self.rename_profile),
+ (':profile_browse*clicked', self.browse_profile),
+ (':profile_delete*clicked', self.delete_profile),
+ (':profile_save*clicked', self.save_profile),
+ (':installation_path_change*clicked', self.new_build_path),
+ (':choose_project_combo*changed', self.switch_project),
+ (':new_project*clicked', self.get_new_project_name),
+ (':project_delete*clicked', self.delete_project),
+ )
+
+
+ def setup(self):
+ # Initialize project combobox
+ self.projects = fss('get_projects')
+ self.project_name = fss('get_project')
+ try:
+ pix = self.projects.index(self.project_name)
+ except:
+ self.switch_project(0)
+ return
+ ui.command(':choose_project_combo.set', self.projects, pix)
+ # Initialize profile combobox
+ self.profiles = fss('get_profiles')
+ self.profile_name = fss('get_profile')
+ try:
+ pfix = self.profiles.index(self.profile_name)
+ except:
+ self.switch_profile(0)
+ pfix = 0
+ ui.command(':choose_profile_combo.set', self.profiles, pfix)
+ # Initialize installation_dir display
+ self.set_build_dir(fss('get_installation_dir'))
+
+
+ def enter(self):
+ """This is called when the page is entered/selected/shown.
+ It performs initializations which depend on the state.
+ """
+ docviewer.gohome('gui_project_settings.html')
+
+
+ def data(self, key):
+ return ui.command('project_page_data.get', key)
+
+
+ def set_build_dir(self, path):
+ self.build_dir = path
+ ui.command(':installation_path_show.text', self.build_dir)
+ ui.enable_installation_page(self.build_dir != '/')
+
+
+ def switch_profile(self, index):
+ """This has no effect on the display!
+ It is assumed that the display is already updated, or will be
+ updated later, and that the index is valid, so that the operation
+ cannot fail.
+ """
+ self.profile_name = self.profiles[index]
+ fss('set_profile', self.profile_name)
+
+
+ def browse_profile(self):
+ source = ui.fileDialog(self.data('file_ps'), dirsonly=True,
+ bookmarks=fss('get_profile_bookmarks'),
+ startdir=fss('getitem', 'profile_browse_dir'))
+ if source:
+ fss('setitem', 'profile_browse_dir', os.path.dirname(source))
+ if os.path.basename(source) in self.profiles:
+ if not ui.command('confirmDialog', self.data('prompt_pr')):
+ return
+ if fss('get_new_profile', source):
+ self.setup()
+ else:
+ run_error(self.data('msg_npd') % source)
+
+
+ def rename_profile(self):
+ if fss('can_rename_profile'):
+ ok, new = ui.command('textLineDialog',
+ self.data('prompt_pn'),
+ None, self.profile_name)
+ if ok:
+ new = new.strip()
+ if new in self.profiles:
+ ui.command('warningDialog', self.data('prompt_pe') % new)
+ else:
+ fss('rename_profile', new)
+ self.setup()
+ else:
+ ui.command('infoDialog', self.data('msg_pu'))
+
+
+ def save_profile(self):
+ bookmarks = fss('get_profile_bookmarks')
+ startdir = fss('getitem', 'profile_browse_dir')
+ path = ui.fileDialog(self.data('file_sp'),
+ create=True, file=self.profile_name,
+ bookmarks=bookmarks,
+ startdir=startdir if startdir else bookmarks[0][0])
+ if path:
+ fss('setitem', 'profile_browse_dir', os.path.dirname(path))
+ ok = fss('save_profile', path, False)
+ if ok == False:
+ if ui.command('confirmDialog', self.data('prompt_dr')):
+ # Force overwrite
+ fss('save_profile', path, True)
+ elif ok == None:
+ run_error(self.data('msg_piu'))
+ else:
+ self.setup()
+
+
+ def delete_profile(self):
+ plist = fss('list_free_profiles')
+ if plist:
+ ok, item = ui.command('listDialog', self.data('prompt_dp'),
+ self.data('delprof'), plist)
+ if ok:
+ if fss('delete_profile', item):
+ self.setup()
+ else:
+ ui.command('infoDialog', self.data('msg_dpff') % item)
+ else:
+ ui.command('infoDialog', self.data('msg_npf'))
+
+
+ def new_build_path(self):
+ # Is anything more necessary? Do I need to test or create the path?
+ # I don't think so, the installation code does that.
+ # If the path is "/", the installation page should be inhibited,
+ # but that is handled by 'setup'.
+ ok, path = ui.command('textLineDialog',
+ self.data('prompt_ip'),
+ None, self.build_dir)
+ if ok:
+ path = fss('set_installation_dir', path)
+ if path:
+ self.set_build_dir(path)
+
+
+ def switch_project(self, index):
+ fss('set_project', self.projects[index])
+ self.setup()
+
+
+ def get_new_project_name(self):
+ ok, name = ui.command('textLineDialog',
+ self.data('prompt_np'),
+ None, self.project_name)
+ if ok:
+ if name in self.projects:
+ run_error(self.data('msg_pe') % name)
+ else:
+ fss('set_project', name)
+ self.setup()
+
+
+ def delete_project(self):
+ """Pop up a list of eligible project names, the selected one
+ will be deleted.
+ """
+ plist = fss('list_free_projects')
+ if plist:
+ ok, item = ui.command('listDialog', self.data('prompt_pd'),
+ self.data('delproj'), plist)
+ if ok:
+ fss('delete_project', item)
+ self.setup()
+ else:
+ ui.command('infoDialog', self.data('msg_np'))
+
+
diff --git a/build_tools/larch7/larch0/gui/front/uim.py b/build_tools/larch7/larch0/gui/front/uim.py
new file mode 100644
index 0000000..71e106b
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/front/uim.py
@@ -0,0 +1,1327 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+#
+# uim.py
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.18.07
+
+#TODO?
+# Fetching of image and icon files via a sort of url-like mechanism, I
+# suppose initially using the 'base:' prefix might be ok.
+# Then the cwd of the gui script would be irrelevant.
+
+#New file dialog for accessing the 'server' end.
+
+
+"""UIM - User Interface Module
+
+The aim is to provide a means of creating graphical user interfaces of
+moderate complexity while abstracting the interface to the actual underlying
+toolkit in such a way that (at least potentially) an alternative toolkit
+could be used.
+[At present this aspect is rather theoretical since only a pyqt based
+version has been written.]
+
+The gui layout is specified as a python data structure, using widget types,
+parameter and signal names independent of the underlying toolkit. All
+widgets are accessible by their tag, which must be specified.
+
+A widget is defined by a call to the 'widget' method of the GuiApp instance.
+The first argument is the widget type, the second is the widget tag, the
+remaining ones must be named, they form the parameters to the constructor.
+If the widget is a 'container' (i.e. if it contains other widgets), it will
+need a 'layout' parameter defining the layout of its contents.
+
+There is also a 'widgetlist' method which accepts a list of widget
+definitions, each definition being itself a list. The first entry in a
+definition is the widget type, the second is the widget tag, the
+third is a dictionary containing all the parameters. For convenience (I'm not
+sure if I will keep this, though) any entries after the dictionary will be
+treated as signal names. These are just added to the parameter dictionary
+with value '' (enabling the signal with its default tag).
+
+Signals have signatures/keys comprising the tag of the emitting widget and
+the signal name (separated by '*'), and this will by default also be the tag
+by which the signal is known for connection purposes. But this can be
+overridden, for example to allow several widgets to emit the same signal.
+In the latter case the widget tag can (optionally) be passed as the first
+argument to the signal handler.
+
+Passing signal names as parameters to a widget constructor enables these
+signals. They can later be disabled, if desired.
+
+Connect and disconnect methods are available, to associate (or dissociate)
+handler functions with (/from) signals.
+"""
+
+import os, sys, traceback, threading
+from PyQt4 import QtGui, QtCore, QtWebKit
+from collections import deque
+#try:
+# import json
+#except:
+# import simplejson as json
+
+#++++++++++++++++++++++++++++++++++++++++++++++++++++
+#TODO
+# Add more widgets
+# Add more attribute handling
+# Add more signal handling
+
+#----------------------------------------------------
+
+def debug(text):
+ sys.stderr.write("GUI: %s\n" % text)
+ sys.stderr.flush()
+
+
+# Widget Base Classes - essentially used as 'Mixins' >>>>>>>>>>>>>>>>
+class WBase:
+ def x__tt(self, text):
+ """Set tooltip.
+ """
+ self.setToolTip(text) #qt
+
+ def x__text(self, text=""):
+ """Set widget text.
+ """
+ self.setText(text) #qt
+
+ def x__enable(self, on):
+ """Enable/Disable widget. on should be True to enable the widget
+ (display it in its normal, active state), False to disable it
+ (which will normally be paler and non-interactive).
+ """
+ self.setEnabled(on) #qt
+
+ def x__focus(self):
+ self.setFocus() #qt
+
+ def x__width(self, w):
+ """Set the minimum width for the widget.
+ """
+ self.setMinimumWidth(w) #qt
+
+ def x__typewriter(self, on):
+ """Use a typewriter (fixed spacing) font.
+ """
+ if on:
+ f = QtGui.QFont(self.font()) #qt
+ f.setFamily("Courier") #qt
+ self.setFont(f) #qt
+
+ def x__busycursor(self, on):
+ """Set/clear the busy-cursor for this widget.
+ """
+ if on:
+ self.setCursor(QtCore.Qt.BusyCursor) #qt
+ else:
+ self.unsetCursor() #qt
+
+
+class BBase:
+ """Button mixin.
+ """
+ def x__icon(self, icon):
+ self.setIcon(self.style().standardIcon(icondict[icon])) #qt
+
+#qt
+icondict = { "left" : QtGui.QStyle.SP_ArrowLeft,
+ "right" : QtGui.QStyle.SP_ArrowRight,
+ "down" : QtGui.QStyle.SP_ArrowDown,
+ "up" : QtGui.QStyle.SP_ArrowUp,
+ "reload" : QtGui.QStyle.SP_BrowserReload,
+ }
+
+class Container:
+ """This just adds layout management for widgets which contain
+ other widgets.
+ """
+ def x__layout(self, layout, immediate=False):
+ """A layout specifies and organizes the contents of a widget.
+ Note that the layouting is not immediately performed by default as
+ it is unlikely that all the contained widgets have been defined yet.
+ """
+ self._layout = layout
+ if immediate:
+ self.x__pack()
+
+ def x__pack(self):
+ """A layout call specifies and organizes the contents of a widget.
+ The layout can be a layout manager list, or a single widget name
+ (or an empty string, which will cause a warning to be issued, but
+ may be useful during development).
+
+ There are three sorts of thing which can appear in layout manager
+ lists (apart from the layout type at the head of the list and an
+ optional attribute dict as second item). There can be named
+ widgets, there can be further layout managers (specified as lists,
+ nested as deeply as you like) and there can be layout widgets,
+ like spacers and separators.
+
+ A layout widget can have optional arguments, which are separated
+ by commas, e.g. 'VLINE,3' passes the argument '3' to the VLINE
+ constructor.
+ """
+ # getattr avoids having to have an __init__() for Container.
+ if getattr(self, '_layout', None):
+ if self._layout != '$':
+ self.setLayout(self.getlayout(self._layout))
+ self._layout = '$'
+ else:
+ debug("No layout set on '%s'" % self.w_name)
+
+ def getlayout(self, item):
+ if isinstance(item, list):
+ try:
+ # Create a layout manager instance
+ layoutmanager = layout_table[item[0]]()
+ assert isinstance(layoutmanager, Layout)
+ except:
+ gui_error("Unknown layout type: %s" % item[0])
+ if (len(item) > 1) and isinstance(item[1], dict):
+ dictarg = item[1]
+ ilist = item[2:]
+ else:
+ dictarg = {}
+ ilist = item[1:]
+ # Build up the list of objects to lay out
+ # If the layout manager is a GRID, accept only grid rows ('+')
+ if isinstance(layoutmanager, GRID):
+ args = []
+ rowlen = None
+ for i in ilist:
+ if isinstance(i, list) and (i[0] == '+'):
+ args.append(self.getlayoutlist(i[1:], grid=True))
+ if rowlen == None:
+ rowlen = len(i)
+ elif len(i) != rowlen:
+ gui_error("Grid (%s) row lengths unequal"
+ % self.w_name)
+ else:
+ gui_error("Grid (%s) layouts must consist of grid"
+ " rows ('+')" % self.w_name)
+ else:
+ # Otherwise the elements of the argument list can be:
+ # A sub-layout
+ # A widget
+ # A SPACE
+ args = self.getlayoutlist(ilist)
+ layoutmanager.do_layout(args)
+ # Attributes
+ for key, val in dictarg:
+ handler = "x__" + key
+ if hasattr(layoutmanager, handler):
+ getattr(layoutmanager, handler)(val)
+ return layoutmanager
+
+ else:
+ # It must be a widget, which will need to be put in a box (qt)
+ return self.getlayout(['VBOX', item])
+
+ def getlayoutlist(self, items, grid=False):
+ objects = []
+ for i in items:
+ if isinstance(i, list):
+ obj = self.getlayout(i)
+ else:
+ parts = i.split(',')
+ i = parts[0]
+ args = parts[1:]
+ try:
+ obj = layout_table[i](*args)
+ if not (isinstance(obj, SPACE) # or a separator line
+ or isinstance(obj, QtGui.QWidget)): #qt
+ assert (grid and isinstance(obj, Span))
+ except:
+ obj = guiapp.getwidget(i)
+ if obj != None:
+ if isinstance(obj, Container):
+ obj.x__pack()
+ else:
+ gui_error("Bad item in layout of '%s': '%s'"
+ % (self.w_name, i))
+ objects.append(obj)
+ return objects
+
+
+class XContainer(Container):
+ """This is a mixin class for containers which can contain more than
+ one layout.
+ """
+ def x__layout(self, layout):
+ gui_error("An extended container (%s) has no 'layout' method"
+ % self.w_name)
+
+
+class TopLevel(Container):
+ def x__show(self):
+ self.set_visible()
+
+ def set_visible(self, on=True):
+ self.setVisible(on) #qt
+
+ def x__size(self, w_h):
+ w, h = [int(i) for i in w_h.split("_")]
+ self.resize(w, h) #qt
+
+ def x__icon(self, iconpath):
+ guiapp.setWindowIcon(QtGui.QIcon(iconpath)) #qt
+
+ def x__title(self, text):
+ self.setWindowTitle(text) #qt
+
+ def x__getSize(self):
+ s = self.size() #qt
+ return "%d_%d" % (s.width(), s.height()) #qt
+
+ def x__getScreenSize(self):
+ dw = guiapp.desktop() #qt
+ geom = dw.screenGeometry(self) #qt
+ return "%d_%d" % (geom.width(), geom.height()) #qt
+
+#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+class Window(QtGui.QWidget, TopLevel): #qt
+ """This is needed to trap window closing events. It also supports
+ a 'busy' mechanism.
+ """
+ def __init__(self):
+ QtGui.QWidget.__init__(self) #qt
+ self.closesignal = ""
+ self.busystate = False
+ self.busy_lock = threading.Lock()
+
+ def closeEvent(self, event): #qt
+ if self.closesignal:
+ guiapp.sendsignal(self.closesignal)
+ event.ignore() #qt
+ return
+ QtGui.QWidget.closeEvent(self, event) #qt
+
+ def x__closesignal(self, text):
+ self.closesignal = text
+
+ def x__busy(self, widgets, on, busycursor=True):
+ """This activates (or deactivates, for on=False) a 'busy' mechanism,
+ which can be one or both of the following:
+ Make the application's cursor change to the 'busy cursor'.
+ Disable a group of widgets.
+ There is a lock to prevent the busy state from being set when it
+ is already active.
+ """
+ # I couldn't get the following calls to work:
+ # w.setCursor(QtCore.Qt.BusyCursor)
+ # w.unsetCursor()
+ self.busy_lock.acquire()
+ if on:
+ if self.busystate:
+ debug("*ERROR* Attempt to set busy state twice")
+ self.busy_lock.release()
+ return
+ self.busycursor = busycursor
+ if busycursor:
+ guiapp.setOverrideCursor(QtCore.Qt.BusyCursor) #qt
+ else:
+ if not self.busystate:
+ debug("*ERROR* Attempt to release busy state twice")
+ self.busy_lock.release()
+ return
+ if self.busycursor:
+ guiapp.restoreOverrideCursor() #qt
+ self.busystate = on
+ self.busy_lock.release()
+ for wn in widgets:
+ w = guiapp.getwidget(wn)
+ if w:
+ w.setEnabled(not on) #qt
+ else:
+ debug("*ERROR* No widget '%s'" % wn)
+
+
+class Dialog(QtGui.QDialog, TopLevel):
+ def __init__(self):
+ QtGui.QDialog.__init__(self) #qt
+
+ def x__showmodal(self):
+ return self.exec_() == QtGui.QDialog.Accepted #qt
+
+
+class DialogButtons(QtGui.QDialogButtonBox): #qt
+ def __init__(self):
+ return
+
+ def x__buttons(self, args):
+ """This keyword argument MUST be present.
+ """
+ buttons = 0
+ for a in args:
+ try:
+ b = getattr(QtGui.QDialogButtonBox, a) #qt
+ assert isinstance(b, int) #qt
+ buttons |= b #qt
+ except:
+ gui_warning("Unknown Dialog button: %s" % a)
+ QtGui.QDialogButtonBox.__init__(self, buttons) #qt
+
+ def x__dialog(self, dname):
+ """This must be set or else the dialog buttons won't do anything.
+ """
+ self._dialog = guiapp.getwidget(dname)
+ self.connect(self, QtCore.SIGNAL("clicked(QAbstractButton *)"), #qt
+ self._clicked) #qt
+
+ def _clicked(self, button): #qt
+ if self.buttonRole(button) == self.AcceptRole: #qt
+ self._dialog.accept() #qt
+ else:
+ self._dialog.reject() #qt
+
+
+def textLineDialog(label=None, title=None, text="", pw=False):
+ if label == None:
+ label = "Enter the value here:"
+ if title == None:
+ title = "Enter Information"
+ if pw:
+ echo = QtGui.QLineEdit.Password #qt
+ else:
+ echo = QtGui.QLineEdit.Normal #qt
+ result, ok = QtGui.QInputDialog.getText(None, #qt
+ title, label, echo, text) #qt
+ return (ok, unicode(result))
+
+
+def listDialog(label=None, title=None, items=[], current=0):
+ if label == None:
+ label = "Choose one of the entries"
+ if title == None:
+ title = "Select an item"
+ item, ok = QtGui.QInputDialog.getItem(None, title, label, items,
+ current, editable=False) #qt
+ return (ok, unicode(item))
+
+
+def confirmDialog(message, title=None):
+ if title == None:
+ title = "Confirmation"
+ return (QtGui.QMessageBox.question(None, title, message, #qt
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) == #qt
+ QtGui.QMessageBox.Yes) #qt
+
+
+def infoDialog(message, title=None):
+ if title == None:
+ title = "Information"
+ QtGui.QMessageBox.information(None, title, message) #qt
+
+
+#+++++++++++++++++++++++++++
+# Error handling
+def gui_error(message, title=None):
+ if title == None:
+ title = "Error"
+ QtGui.QMessageBox.critical(None, title, message) #qt
+ guiapp.exit(1) #qt
+
+def gui_warning(message, title=None):
+ if title == None:
+ title = "Warning"
+ QtGui.QMessageBox.warning(None, title, message) #qt
+
+def onexcept(text):
+ debug(traceback.format_exc())
+ gui_error(text, "Exception")
+#---------------------------
+
+fileDialogDir = "/"
+def fileDialog(message, start=None, title=None, dir=False, create=False,
+ file=None, urls=None, filter=None):
+ # filter is a list: first a textual description, then acceptable glob filenames
+ global fileDialogDir
+ if not start:
+ start = fileDialogDir
+ dlg = QtGui.QFileDialog(None, message, start) #qt
+ if title:
+ dlg.setWindowTitle(title) #qt
+ dlg.setReadOnly(not create) #qt
+ if dir:
+ dlg.setFileMode(dlg.Directory) #qt
+ elif not create:
+ dlg.setFileMode(dlg.ExistingFile) #qt
+ if filter:
+ dlg.setNameFilter("%s (%s)" % (filter[0], " ".join(filter[1:]))) #qt
+
+ if urls:
+ urlsqt = [ QtCore.QUrl.fromLocalFile(u[0]) for u in urls ] #qt
+ urlsqt.append(QtCore.QUrl.fromLocalFile(
+ QtGui.QDesktopServices.storageLocation(
+ QtGui.QDesktopServices.HomeLocation)))
+
+ dlg.setSidebarUrls(urlsqt) #qt
+
+ if file:
+ dlg.selectFile(file)
+
+ if dlg.exec_():
+ path = str(dlg.selectedFiles()[0]).strip()
+ if os.path.isdir(path):
+ fileDialogDir = path
+ elif os.path.isfile(path):
+ fileDialogDir = os.path.dirname(path)
+ return path
+ else:
+ return ""
+
+
+class Stack(QtGui.QStackedWidget, XContainer): #qt
+ def __init__(self):
+ QtGui.QStackedWidget.__init__(self) #qt
+ self.x_twidgets = []
+
+ def x__pages(self, pages):
+ self.x_twidgets = pages
+
+ def x__pack(self):
+ for name in self.x_twidgets:
+ w = guiapp.getwidget(name)
+ w.x__pack()
+ self.addWidget(w) #qt
+
+ def x__set(self, index=0):
+ self.setCurrentIndex(index) #qt
+
+
+class Notebook(QtGui.QTabWidget, XContainer): #qt
+ def __init__(self):
+ QtGui.QTabWidget.__init__(self) #qt
+ self.x_tabs = []
+ self.x_twidgets = []
+
+ def x__changed(self, name=''):
+ guiapp.signal(self, 'changed', name, 'currentChanged(int)') #qt
+
+ def x__tabs(self, tabs):
+ self.x_twidgets = tabs
+
+ def x__pack(self):
+ for name, title in self.x_twidgets:
+ w = guiapp.getwidget(name)
+ w.x__pack()
+ self.addTab(w, title) #qt
+ self.x_tabs.append([name, w])
+
+ def x__set(self, index=0):
+ self.setCurrentIndex(index) #qt
+
+ def x__enableTab(self, index, on):
+ self.setTabEnabled(index, on) #qt
+
+
+class Page(QtGui.QWidget, Container): #qt
+ def __init__(self):
+ QtGui.QWidget.__init__(self) #qt
+
+ def x__enable(self, on):
+ """Enable/Disable widget. on should be True to enable the widget
+ (display it in its normal, active state), False to disable it
+ (which will normally be paler and non-interactive).
+ """
+ self.setEnabled(on) #qt
+
+
+class Frame(QtGui.QGroupBox, WBase, Container): #qt
+ def __init__(self):
+ QtGui.QGroupBox.__init__(self) #qt
+ self._text = None
+
+ def x__text(self, text):
+ self._text = text
+ self.setTitle(text) #qt
+
+# A hack to improve spacing
+ def setLayout(self, layout):
+ topgap = 10 if self._text else 0
+ layout.setContentsMargins(0, topgap, 0, 0) #qt
+ QtGui.QGroupBox.setLayout(self, layout)
+
+
+class OptionalFrame(Frame): #qt
+ def __init__(self): #qt
+ Frame.__init__(self) #qt
+ self.setCheckable(True) #qt
+ self.setChecked(False) #qt
+
+ def x__toggled(self, name=''):
+ guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
+
+ def x__opton(self, on):
+ self.setChecked(on) #qt
+
+#TODO: Is this still needed? (I think it's a qt bug)
+ def x__enable_hack(self): #qt
+ if not self.isChecked(): #qt
+ self.setChecked(True) #qt
+ self.setChecked(False) #qt
+
+ def x__active(self):
+ return self.isChecked() #qt
+
+
+def read_markup(markup):
+ def read_markup0(mlist):
+ text = ''
+ for i in mlist:
+ text += read_markup(i) if isinstance(i, list) else i
+ return text
+ tag = markup[0]
+ if tag == '':
+ return read_markup0(markup[1:])
+ elif tag in ('h1', 'h2', 'h3', 'h4', 'p', 'em', 'strong'):
+ return '<%s>%s</%s>' % (tag, read_markup0(markup[1:]), tag)
+ elif tag == 'color':
+ return '<span style="color:%s;">%s</span>' % (markup[1],
+ read_markup0(markup[2:]))
+ return "Markup parse error"
+
+
+class Label(QtGui.QLabel, WBase): #qt
+ def __init__(self):
+ QtGui.QLabel.__init__(self) #qt
+
+ def x__markup(self, markup):
+ self.setText(read_markup(markup)) #qt
+
+ def x__image(self, path):
+ self.setPixmap(QtGui.QPixmap(path)) #qt
+
+ def x__align(self, pos):
+ if pos == "center":
+ a = QtCore.Qt.AlignCenter #qt
+ else:
+ a = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter #qt
+ self.setAlignment(a) #qt
+
+
+class Button(QtGui.QPushButton, WBase, BBase): #qt
+ def __init__(self):
+ QtGui.QPushButton.__init__(self) #qt
+
+ def x__clicked(self, name=''):
+ guiapp.signal(self, 'clicked', name, 'clicked()') #qt
+
+
+class ToggleButton(QtGui.QPushButton, WBase, BBase): #qt
+ def __init__(self):
+ QtGui.QPushButton.__init__(self) #qt
+ self.setCheckable(True) #qt
+
+ def x__toggled(self, name=''):
+ guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
+
+ def x__set(self, on):
+ self.setChecked(on) #qt
+
+
+class CheckBox(QtGui.QCheckBox, WBase): #qt
+ def __init__(self):
+ QtGui.QCheckBox.__init__(self) #qt
+
+ def x__toggled(self, name=''):
+ # A bit of work is needed to get True/False state #qt
+ # instead of 0/1/2 #qt
+ guiapp.signal(self, 'toggled', name,
+ 'toggled(bool)', self.s_toggled) #qt
+
+ def s_toggled(self, state): #qt
+ """Convert the argument to True/False.
+ """ #qt
+ return (state != QtCore.Qt.Unchecked,) #qt
+
+ def x__set(self, on):
+ self.setCheckState(2 if on else 0) #qt
+
+ def x__active(self):
+ return self.checkState() != QtCore.Qt.Unchecked #qt
+
+
+class RadioButton(QtGui.QRadioButton, WBase): #qt
+ def __init__(self):
+ QtGui.QPushButton.__init__(self) #qt
+
+ def x__toggled(self, name=''):
+ guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
+
+ def x__set(self, on):
+ self.setChecked(on) #qt
+
+ def x__active(self):
+ return self.isChecked() #qt
+
+
+class ComboBox(QtGui.QComboBox, WBase): #qt
+ def __init__(self):
+ QtGui.QComboBox.__init__(self) #qt
+
+ def x__changed(self, name=''):
+ guiapp.signal(self, 'changed', name, 'currentIndexChanged(int)') #qt
+
+ def x__changedstr(self, name=''):
+ guiapp.signal(self, 'changedstr', name,
+ 'currentIndexChanged(const QString &)') #qt
+
+ def x__set(self, items, index=0):
+ self.blockSignals(True)
+ self.clear() #qt
+ if items:
+ self.addItems(items) #qt
+ self.setCurrentIndex(index) #qt
+ self.blockSignals(False)
+
+
+class ListChoice(QtGui.QListWidget, WBase): #qt
+ def __init__(self):
+ QtGui.QListWidget.__init__(self) #qt
+
+ def x__changed(self, name=''):
+ guiapp.signal(self, 'changed', name, 'currentRowChanged(int)') #qt
+
+ def x__set(self, items, index=0):
+ self.blockSignals(True)
+ self.clear() #qt
+ if items:
+ self.addItems(items) #qt
+ self.setCurrentRow(index) #qt
+ self.blockSignals(False)
+
+
+class List(QtGui.QTreeWidget, WBase): #qt
+ # Only using top-level items of the tree
+ def __init__(self):
+ QtGui.QTreeWidget.__init__(self) #qt
+ self.mode = ""
+ self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) #qt
+ self.setRootIsDecorated(False) #qt
+ self._hcompact = False # used for scheduling header-compaction
+
+ def x__select(self, name=''):
+ guiapp.signal(self, 'select', name,
+ 'itemSelectionChanged()', self.s_select) #qt
+
+ def x__clicked(self, name=''):
+ guiapp.signal(self, 'clicked', name,
+ 'itemClicked(QTreeWidgetItem *,int)', self.s_clicked) #qt
+
+ def s_select(self):
+ # Signal a selection change, passing the new selection list (indexes)
+ s = [self.indexOfTopLevelItem(i) for i in self.selectedItems()] #qt
+ if self.mode == "Single":
+ return s
+ else:
+ return (s,)
+
+ def s_clicked(self, item, col): #qt
+ """This is intended for activating a user-defined editing function.
+ Tests showed that this is called after the selection is changed, so
+ if using this signal, use it only in 'Single' selection mode and
+ use this, not 'select' to record selection changes. Clicking on the
+ selected row should start editing the cell, otherwise just change
+ the selection.
+ """
+ ix = self.indexOfTopLevelItem(item) #qt
+ return (ix, col)
+
+ def x__selectionmode(self, sm):
+ self.mode = sm
+ if sm == "None":
+ self.setSelectionMode(QtGui.QAbstractItemView.NoSelection) #qt
+ elif sm == "Single":
+ self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) #qt
+ else:
+ self.mode = ""
+ self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) #qt
+
+ def x__headers(self, headers): #qt
+ self.setHeaderLabels(headers) #qt
+ if self._hcompact:
+ self._compact()
+
+ def x__set(self, items, index=0): #qt
+ # Note that each item must be a tuple/list containing
+ # entries for each column.
+ self.clear() #qt
+ c = 0
+ for i in items:
+ item = QtGui.QTreeWidgetItem(self, i) #qt
+ self.addTopLevelItem(item) #qt
+ if c == index:
+ self.setCurrentItem(item)
+ c += 1
+ if self._hcompact:
+ self._compact()
+
+ def x__compact(self, on=True):
+ self._hcompact = on
+ if on:
+ self._compact()
+
+ def _compact(self):
+ for i in range(self.columnCount()): #qt
+ self.resizeColumnToContents(i) #qt
+
+
+class LineEdit(QtGui.QLineEdit, WBase): #qt
+ def __init__(self):
+ QtGui.QLineEdit.__init__(self) #qt
+
+ def x__enter(self, name=''):
+ guiapp.signal(self, 'enter', name, 'returnPressed()') #qt
+
+ def x__changed(self, name=''):
+ guiapp.signal(self, 'changed', name, 'textEdited(const QString &)') #qt
+
+ def x__get(self):
+ return unicode(self.text()) #qt
+
+ def x__ro(self, ro):
+ self.setReadOnly(ro) #qt
+
+ def x__pw(self, star):
+ self.setEchoMode(QtGui.QLineEdit.Password if star == "+" #qt
+ else QtGui.QLineEdit.NoEcho if star == "-" #qt
+ else QtGui.QLineEdit.Normal) #qt
+
+
+class CheckList(QtGui.QWidget, WBase): #qt
+ def __init__(self):
+ QtGui.QWidget.__init__(self) #qt
+ self.box = QtGui.QVBoxLayout(self) #qt
+ self.title = None
+ if text: #qt
+ l.addWidget(QtGui.QLabel(text)) #qt
+ self.widget = QtGui.QListWidget() #qt
+ l.addWidget(self.widget) #qt
+
+ def x__title(self, text):
+ if self.title:
+ self.title.setText(text) #qt
+ else:
+ self.title = QtGui.QLabel(text) #qt
+ self.box.insertWidget(0, self.title) #qt
+
+ def x__checked(self, index):
+ return (self.widget.item(index).checkState() == #qt
+ QtCore.Qt.Checked) #qt
+
+ def x__set(self, items):
+ self.widget.blockSignals(True) #qt
+ self.widget.clear() #qt
+ if items:
+ for s, c in items:
+ wi = QtGui.QListWidgetItem(s, self.widget) #qt
+ wi.setCheckState(QtCore.Qt.Checked if c #qt
+ else QtCore.Qt.Unchecked) #qt
+ self.blockSignals(False) #qt
+
+
+class TextEdit(QtGui.QTextEdit, WBase): #qt
+ def __init__(self):
+ QtGui.QTextEdit.__init__(self) #qt
+
+ def x__ro(self, ro):
+ self.setReadOnly(ro) #qt
+
+ def x__append_and_scroll(self, text):
+ self.append(text) #qt
+ self.ensureCursorVisible() #qt
+
+ def x__get(self):
+ return unicode(self.toPlainText()) #qt
+
+ def x__undo(self):
+ QtGui.QTextEdit.undo(self) #qt
+
+ def x__redo(self):
+ QtGui.QTextEdit.redo(self) #qt
+
+ def x__copy(self):
+ QtGui.QTextEdit.copy(self) #qt
+
+ def x__cut(self):
+ QtGui.QTextEdit.cut(self) #qt
+
+ def x__paste(self):
+ QtGui.QTextEdit.paste(self) #qt
+
+
+class HtmlView(QtWebKit.QWebView, WBase): #qt
+ def __init__(self):
+ QtWebKit.QWebView.__init__(self) #qt
+
+ def x__html(self, content):
+ self.setHtml(content) #qt
+
+ def x__setUrl(self, url):
+ self.load(QtCore.QUrl(url)) #qt
+
+ def x__prev(self):
+ self.back() #qt
+
+ def x__next(self):
+ self.forward() #qt
+
+
+class SpinBox(QtGui.QDoubleSpinBox, WBase): #qt
+ def __init__(self):
+ QtGui.QDoubleSpinBox.__init__(self) #qt
+ self.step = None
+
+ def x__changed(self, name=''):
+ guiapp.signal(self, 'changed', name, 'valueChanged(double)') #qt
+
+ def x__min(self, min):
+ self.setMinimum(min)
+
+ def x__max(self, max):
+ self.setMaximum(max)
+
+ def x__decimals(self, dec):
+ self.setDecimals(dec)
+ if not self.step:
+ self.setSingleStep(10**(-dec))
+
+ def x__step(self, step):
+ self.setSingleStep(step)
+
+ def x__value(self, val):
+ self.setValue(val)
+
+
+class ProgressBar(QtGui.QProgressBar, WBase): #qt
+ def __init__(self):
+ QtGui.QProgressBar.__init__(self) #qt
+
+ def x__set(self, value):
+ self.setValue(value) #qt
+
+ def x__max(self, max):
+ self.setMaximum(max) #qt
+
+
+
+# Layout classes
+class Layout:
+ """A mixin base class for all layout widgets.
+ """
+ pass
+
+boxmargin=3
+class _BOX(Layout):
+ def do_layout(self, items):
+ self.setContentsMargins(boxmargin, boxmargin, boxmargin, boxmargin) #qt
+ for wl in items:
+ if isinstance(wl, QtGui.QWidget): #qt
+ self.addWidget(wl) #qt
+ elif isinstance(wl, SPACE): #qt
+ if wl.size: #qt
+ self.addSpacing(wl.size) #qt
+ self.addStretch() #qt
+ elif isinstance(wl, Layout): #qt
+ self.addLayout(wl) #qt
+ else: #qt
+ gui_error("Invalid Box entry: %s" % repr(wl))
+
+
+class VBOX(QtGui.QVBoxLayout, _BOX): #qt
+ def __init__(self):
+ QtGui.QVBoxLayout.__init__(self) #qt
+
+
+class HBOX(QtGui.QHBoxLayout, _BOX): #qt
+ def __init__(self):
+ QtGui.QHBoxLayout.__init__(self) #qt
+
+
+class GRID(QtGui.QGridLayout, Layout): #qt
+ def __init__(self):
+ QtGui.QGridLayout.__init__(self) #qt
+
+ def do_layout(self, rows):
+ y = -1
+ for row in rows:
+ y += 1
+ x = -1
+ for wl in row:
+ x += 1
+ if isinstance(wl, Span):
+ continue
+ # Determine the row and column spans
+ x1 = x + 1
+ while (x1 < len(row)) and isinstance(row[x1], CSPAN):
+ x1 += 1
+ y1 = y + 1
+ while (y1 < len(rows)) and isinstance(rows[y1][x], RSPAN):
+ y1 += 1
+
+ if isinstance(wl, QtGui.QWidget): #qt
+ self.addWidget(wl, y, x, y1-y, x1-x) #qt
+ elif isinstance(wl, Layout):
+ self.addLayout(wl, y, x, y1-y, x1-x) #qt
+ elif isinstance(wl, SPACE):
+ self.addItem(QtGui.QSpacerItem(wl.size, wl.height),
+ y, x, y1-y, x1-x) #qt
+ else:
+ gui_error("Invalid entry in Grid layout: %s" % repr(wl))
+
+
+class SPACE:
+ """Can be used in boxes and grids. In boxes only size is of interest,
+ and it also means vertical size in the case of a vbox. In grids size
+ is the width.
+ """
+ def __init__(self, size_width='0', height='0'): #qt
+ self.size = int(size_width) #qt
+ self.height = int(height) #qt
+
+
+class Span:
+ """Class to group special grid layout objects together - it doesn't
+ actually do anything itself, but is used for checking object types.
+ """
+ pass
+
+
+class CSPAN(Span):
+ """Column-span layout item. It doesn't do anything itself, but it is used
+ by the Grid layout constructor.
+ """
+ pass
+
+
+class RSPAN(Span):
+ """Row-span layout item. It doesn't do anything itself, but it is used
+ by the Grid layout constructor.
+ """
+ pass
+
+
+class HLINE(QtGui.QFrame): #qt
+ def __init__(self, pad=None):
+ QtGui.QFrame.__init__(self) #qt
+ self.setFrameShape(QtGui.QFrame.HLine) #qt
+ if pad:
+ self.setFixedHeight(1 + 2*int(pad)) #qt
+
+
+class VLINE(QtGui.QFrame): #qt
+ def __init__(self, pad=None):
+ QtGui.QFrame.__init__(self) #qt
+ self.setFrameShape(QtGui.QFrame.VLine) #qt
+ if pad:
+ self.setFixedWidth(1 + 2*int(pad)) #qt
+
+
+class DATA:
+ """This is not really a widget, it just holds a dictionary of
+ potentially internationalized messages.
+ """
+ def x__messages(self, mdict):
+ self.messages = mdict
+
+ def x__get(self, key):
+ return self.messages.get(key)
+
+
+class Uim(QtGui.QApplication):
+ """This class represents an application gui, possibly with more than
+ one top level window.
+ """
+ timers = [] # timer objects
+
+ def __init__(self):
+ global guiapp
+ guiapp = self
+ self.eno = QtCore.QEvent.registerEventType() #qt
+ QtGui.QApplication.__init__(self, []) #qt
+ self.setQuitOnLastWindowClosed(False) #qt
+
+ self.widgets = {} # all widgets, key = widget tag
+ self.signal_dict = {} # signal connections, key = signature
+
+ # callback list for event loop: (callback, arglist) pairs:
+ self.idle_calls = deque()
+
+
+ def event(self, e):
+ if e.type() == self.eno:
+ # Process item from list
+ cb, a = self.idle_calls.popleft()
+ cb(*a)
+ return True
+ else:
+ return QtGui.QApplication.event(self, e) #qt
+
+
+ def run(self):
+ self.exec_() #qt
+
+
+# def quit(self):
+# self.quit() #qt
+
+
+ def addwidget(self, fullname, wo):
+ if self.widgets.has_key(fullname):
+ gui_error("Attempted to define widget '%s' twice." % fullname)
+ self.widgets[fullname] = wo
+
+
+ def getwidget(self, w):
+ widget = self.widgets.get(w)
+ if widget == None:
+ gui_warning("Unknown widget: %s" % w)
+ return widget
+
+
+ def show(self, windowname):
+ self.getwidget(windowname).setVisible()
+
+
+ def command(self, cmdtext, *args):
+ cmd = specials_table.get(cmdtext)
+ if not cmd:
+ w, m = cmdtext.split(".")
+ wo = self.getwidget(w)
+ cmd = getattr(wo, 'x__' + m)
+ return cmd(*args)
+
+
+ def widget(self, wtype, wname, args):
+ wobj = widget_table[wtype]()
+ wobj.w_name = wname
+
+ # Attributes
+ for key, val in args.iteritems():
+ handler = "x__" + key
+ if hasattr(wobj, handler):
+ getattr(wobj, handler)(val)
+# Unrecognized attributes are ignored ...
+
+ self.addwidget(wname, wobj)
+
+
+ def widgetlist(self, wlist):
+ for w in wlist:
+ # Add simple signals
+ for s in w[3:]:
+ w[2][s] = ''
+ self.widget(w[0], w[1], w[2])
+
+
+ def signal(self, source, signal, name=None, xsignal=None, convert=None):
+ """Enable or disable a signal.
+ Signal.signals is a dictionary of enabled signals.
+ The key is constructed from the widget name and the formal signal name.
+ The name of the signal which actually gets generated will be the
+ same as the key unless the 'name' parameter is set. See the
+ 'Signal' class for further details.
+ If 'name' is None (not ''!), the signal will be disabled.
+ """
+ widsig = source.w_name + '*' + signal
+ if name == None:
+ s = Signal.signals.get(widsig)
+ if not s:
+ gui_error("Can't disable signal '%s' - it's not enabled"
+ % widsig)
+ s.disconnect() # Probably not necessary in qt
+ del(Signal.signals[widsig])
+ else:
+ if Signal.signals.has_key(widsig):
+ gui_error("Signal already connected: %s" % widsig)
+ Signal.signals[widsig] = Signal(source, signal, name, xsignal,
+ convert)
+
+
+ def connect(self, signal, function):
+ if self.signal_dict.has_key(signal):
+ self.signal_dict[signal].append(function)
+ else:
+ self.signal_dict[signal] = [function]
+
+
+ def connectlist(self, *slotlist):
+ for s in slotlist:
+ self.connect(*s)
+
+
+ def disconnect(self, signal, function):
+ try:
+ l = self.signal_dict[signal]
+ l.remove(function)
+ except:
+ gui_error("Slot disconnection for signal '%s' failed"
+ % signal)
+
+
+ def sendsignal(self, name, *args):
+ # When there are no slots a debug message is output.
+ slots = self.signal_dict.get(name)
+ if slots:
+ try:
+ for slot in slots:
+ slot(*args)
+ except:
+ gui_error("Signal handling error:\n %s"
+ % traceback.format_exc())
+ else:
+ debug("Unhandled signal: %s %s" % (name, repr(args)))
+
+
+ def idle_add(self, callback, *args):
+ self.idle_calls.append((callback, args))
+ e = QtCore.QEvent(self.eno) #qt
+ self.postEvent(self, e) #qt
+
+
+ def timer(self, callback, period):
+ """Start a timer which calls the callback function on timeout.
+ Only if the callback returns True will the timer be retriggered.
+ """
+ Uim.timers.append(Timer(callback, period))
+
+
+class Timer(QtCore.QTimer): #qt
+ def __init__(self, timers, callback, period):
+ QtCore.QTimer.__init__(self) #qt
+ self.x_callback = callback
+ self.connect(self, QtCore.SIGNAL("timeout()"), #qt
+ self.x_timeout)
+ self.start(int(period * 1000)) #qt
+
+ def x_timeout(self):
+ if not self.x_callback():
+ self.stop() #qt
+ Uim.timers.remove(self)
+
+
+
+class Signal:
+ """Each instance represents a single connection.
+ """
+ signals = {} # Enabled signals
+
+ def __init__(self, source, signal, name, xsignal, convert):
+ """'source' is the widget object which initiates the signal.
+ 'signal' is the signal name.
+ If 'name' is given (not empty), the signal will get this as its name,
+ and this name may be used for more than one connection.
+ Otherwise the name is built from the name of the source widget and
+ the signal type as 'source*signal' and this is unique.
+ If 'name' begins with '+' an additional argument, the source
+ widget name, will be inserted at the head of the argument list.
+ 'xsignal' is a toolkit specific signal descriptor.
+ 'convert' is an optional function (default None) - toolkit specific -
+ to perform signal argument conversions.
+ """
+ self.widsig = '%s*%s' % (source.w_name, signal)
+ #+ For disconnect?
+ self.xsignal = xsignal
+ #-
+ self.convert = convert # Argument conversion function
+ self.tag = name if name else self.widsig
+ self.wname = source.w_name if self.tag[0] == '+' else None
+ if not source.connect(source, QtCore.SIGNAL(xsignal), #qt
+ self.signal):
+ gui_error("Couldn't enable signal '%s'" % self.widsig)
+
+ def signal(self, *args):
+ if self.convert:
+ args = self.convert(*args)
+ if self.wname:
+ guiapp.sendsignal(self.tag, self.wname, *args)
+ else:
+ guiapp.sendsignal(self.tag, *args)
+
+ def disconnect(self):
+ w = guiapp.getwidget(self.widsig.split('*')[0])
+ w.disconnect(w, QtCore.SIGNAL(self.xsignal), self.signal) #qt
+
+
+
+#+++++++++++++++++++++++++++
+# Catch all unhandled errors.
+def errorTrap(type, value, tb):
+ etext = "".join(traceback.format_exception(type, value, tb))
+ debug(etext)
+ gui_error(etext, "This error could not be handled.")
+
+sys.excepthook = errorTrap
+#---------------------------
+
+widget_table = {
+ "DATA": DATA,
+ "Window": Window,
+ "Dialog": Dialog,
+ "DialogButtons": DialogButtons,
+ "Notebook": Notebook,
+ "Stack": Stack,
+ "Page": Page,
+ "Frame": Frame,
+ "Button": Button,
+ "ToggleButton": ToggleButton,
+ "RadioButton": RadioButton,
+ "CheckBox": CheckBox,
+ "Label": Label,
+ "CheckList": CheckList,
+ "List": List,
+ "OptionalFrame": OptionalFrame,
+ "ComboBox": ComboBox,
+ "ListChoice": ListChoice,
+ "LineEdit": LineEdit,
+ "TextEdit": TextEdit,
+ "HtmlView": HtmlView,
+ "SpinBox": SpinBox,
+ "ProgressBar": ProgressBar,
+}
+
+specials_table = {
+ "textLineDialog": textLineDialog,
+ "infoDialog": infoDialog,
+ "confirmDialog": confirmDialog,
+ "errorDialog": gui_error,
+ "warningDialog": gui_warning,
+ "fileDialog": fileDialog,
+ "listDialog": listDialog,
+}
+
+layout_table = {
+ "VBOX": VBOX,
+ "HBOX": HBOX,
+ "GRID": GRID,
+# "+": GRIDROW,
+ "-": CSPAN,
+ "|": RSPAN,
+ "*": SPACE,
+ "VLINE": VLINE,
+ "HLINE": HLINE,
+}
+
diff --git a/build_tools/larch7/larch0/gui/larch.py b/build_tools/larch7/larch0/gui/larch.py
new file mode 100755
index 0000000..bddc78c
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/larch.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+#
+# larch.py - GUI for the larch scripts
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+#2010.06.27
+
+import sys, os
+dirname = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(dirname + '/front')
+sys.path.append(os.path.dirname(dirname) + '/cli')
+
+import controller
+from mainwindow import start
+
+# Note that the gui module must have a reference point for relative
+# paths, so the current directory must be set to the larch base directory
+# before starting the gui:
+os.chdir(controller.base_dir)
+start()
diff --git a/build_tools/larch7/larch0/gui/layouts/docviewer.uim b/build_tools/larch7/larch0/gui/layouts/docviewer.uim
new file mode 100644
index 0000000..8e85a0f
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/docviewer.uim
@@ -0,0 +1,72 @@
+# docviewer.uim - The layout for the documentation viewer widget
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.05.21
+
+[
+ ['Frame', 'doc:page',
+ { 'layout':
+ ['VBOX',
+ ['HBOX', 'doc:header', '*', 'doc:back', 'doc:forward',
+ 'doc:home', 'doc:parent', 'doc:hide'],
+ 'doc:content'
+ ]
+ }
+ ],
+ ['Label', 'doc:header',
+ { 'markup': ['h2', _("Documentation")]
+ }
+ ],
+ ['HtmlView', 'doc:content', {}],
+ ['Button', 'doc:hide',
+ { 'text': _("Hide"),
+ 'tt': _("Return to the larch controls"),
+ 'clicked': ''
+ },
+ ],
+ ['Button','doc:back',
+ { 'icon': 'left',
+ 'tt': _("Go back in the viewing history"),
+ 'clicked': ''
+ },
+ ],
+ ['Button','doc:forward',
+ { 'icon': 'right',
+ 'tt': _("Go forward in the viewing history"),
+ 'clicked': ''
+ },
+ ],
+
+ ['Button','doc:home',
+ { 'icon': 'reload',
+ 'tt': _("Reload the documentation for the current larch tab"),
+ 'clicked': ''
+ },
+ ],
+
+ ['Button','doc:parent',
+ { 'icon': 'up',
+ 'tt': _("Go to the general larch documentation index"),
+ 'clicked': ''
+ },
+ ],
+
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/editor.uim b/build_tools/larch7/larch0/gui/layouts/editor.uim
new file mode 100644
index 0000000..2338f6e
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/editor.uim
@@ -0,0 +1,92 @@
+# editor.uim - The layout for the editor widget
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.05.21
+
+[
+ ['Frame', 'edit:page',
+ { 'layout':
+ ['VBOX',
+ ['HBOX', 'edit:header', '*', 'edit:title'],
+ ['HBOX', 'edit:content',
+ ['VBOX', 'edit:copy', 'edit:cut', 'edit:paste',
+ 'edit:undo', 'edit:redo', 'edit:revert',
+ '*', 'edit:cancel', 'edit:ok'
+ ]
+ ]
+ ]
+ }
+ ],
+ ['Label', 'edit:header',
+ { 'markup': ['h2', _("Editor")]
+ }
+ ],
+ ['Label', 'edit:title', {}],
+ ['TextEdit', 'edit:content', {}],
+ ['Button', 'edit:ok',
+ { 'text': _('OK'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:cancel',
+ { 'text': _('Cancel'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:revert',
+ { 'text': _('Revert'),
+ 'tt': _('Restore the text to its initial/default state'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:copy',
+ { 'text': _('Copy'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:cut',
+ { 'text': _('Cut'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:paste',
+ { 'text': _('Paste'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:undo',
+ { 'text': _('Undo'),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'edit:redo',
+ { 'text': _('Redo'),
+ 'clicked': ''
+ }
+ ],
+
+ ['DATA', 'editor_data',
+ { 'messages':
+ { 'msg_dflt': _("Editing '%s'")
+ }
+ },
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/logger.uim b/build_tools/larch7/larch0/gui/layouts/logger.uim
new file mode 100644
index 0000000..0ecb7bf
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/logger.uim
@@ -0,0 +1,57 @@
+# logger.uim - The layout for the logging widget
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.05.22
+
+[
+ ['Frame', 'log:page',
+ { 'layout':
+ ['VBOX',
+ 'log:header',
+ ['HBOX',
+ 'log:text',
+ ['VBOX', 'log:clear', '*', 'log:hide']
+ ]
+ ]
+ }
+ ],
+ ['Label', 'log:header',
+ { 'markup': ['', ['h2', _("Low-level Command Logging")],
+ ['p', _("Here you can follow the detailed, low-level"
+ " progress of the commands.")]]
+ }
+ ],
+ ['TextEdit', 'log:text',
+ { 'ro': True
+ }
+ ],
+ ['Button', 'log:clear',
+ { 'text': _("Clear"),
+ 'clicked': ''
+ }
+ ],
+ ['Button', 'log:hide',
+ { 'text': _("Hide"),
+ 'tt': _("Go back to the larch controls"),
+ 'clicked': ''
+ }
+ ]
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/page_installation.uim b/build_tools/larch7/larch0/gui/layouts/page_installation.uim
new file mode 100644
index 0000000..0949dcd
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_installation.uim
@@ -0,0 +1,179 @@
+# page_installation.uim - The layout for the installation page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.06.24
+
+[
+ ['Page', ':page_installation',
+ { 'layout':
+ ['VBOX',
+ ['HBOX', ':edit_profile', '*', ':pacmanops'],
+ '*', ':editmirrorlist',
+ ':settings_advanced',
+ 'HLINE',
+ ['HBOX', '*', ':install']
+ ]
+ }
+ ],
+ # - - - - The profile editing frame
+ ['Frame', ':edit_profile',
+ { 'text': _("Edit Profile"),
+ 'layout':
+ ['VBOX',
+ ':addedpacks',
+ ':vetopacks',
+ ':pacmanconf',
+ ':repos'
+ ]
+ }
+ ],
+ ['Button', ':addedpacks',
+ { 'text': _("Edit 'addedpacks'"),
+ 'tt': _("Edit the list of packages to be installed")
+ },
+ 'clicked'
+ ],
+ ['Button', ':vetopacks',
+ { 'text': _("Edit 'vetopacks'"),
+ 'tt': _("Edit the list of packages NOT to install")
+ },
+ 'clicked'
+ ],
+ ['Button', ':pacmanconf',
+ { 'text': _("Edit pacman.conf options"),
+ 'tt': _("Edit pacman.conf options - not the repositories")
+ },
+ 'clicked'
+ ],
+ ['Button', ':repos',
+ { 'text': _("Edit pacman.conf repositories"),
+ 'tt': _("Edit the repository entries for pacman.conf")
+ },
+ 'clicked'
+ ],
+
+ # - - - - The installed package tweaking frame
+ ['OptionalFrame', ':pacmanops',
+ { 'text': _("Tweak Installed Packages"),
+ 'layout':
+ ['VBOX', ':sync', ':update', ':add', ':remove']
+ }
+ ],
+ ['Button', ':sync',
+ { 'text': _("Synchronize db"),
+ 'tt': _("Synchronize the pacman db on the target (pacman -Sy)")
+ },
+ 'clicked'
+ ],
+ ['Button', ':update',
+ { 'text': _("Update / Add package [-U]"),
+ 'tt': _("Update / Add a package from a package file"
+ " using pacman -U")
+ },
+ 'clicked'
+ ],
+ ['Button', ':add',
+ { 'text': _("Add package(s) [-S]"),
+ 'tt': _("Add one or more packages (space separated)"
+ " using pacman -S")
+ },
+ 'clicked'
+ ],
+ ['Button', ':remove',
+ { 'text': _("Remove package(s) [-Rs]"),
+ 'tt': _("Remove one or more packages (space separated)"
+ " using pacman -Rs")
+ },
+ 'clicked'
+ ],
+
+ # - - - - The advanced installation options frame
+ ['OptionalFrame', ':settings_advanced',
+ { 'text': _("Advanced Installation Options"),
+ 'layout': ['HBOX', ':installrepos', 'VLINE,3', ':cache']
+ }
+ ],
+
+ ['OptionalFrame', ':installrepos',
+ { 'text': _("Use project repository list"),
+ 'tt': _("Enables use of an alternative pacman.conf"
+ " for installation only"),
+ 'layout':
+ ['HBOX', ':editrepolist']
+ },
+ 'toggled'
+ ],
+ ['Button', ':editrepolist',
+ { 'text': _("Edit repository list"),
+ 'tt': _("Edit repository list file used for installation")
+ },
+ 'clicked'
+ ],
+ ['Button', ':editmirrorlist',
+ { 'text': _("Edit mirror list used for installation only"),
+ 'tt': _("A mirror list for the live system should be placed"
+ " in the overlay")
+ },
+ 'clicked'
+ ],
+
+ ['Frame', ':cache',
+ { 'text': _("Package Cache"),
+ 'layout':
+ ['HBOX', ':cache_show', ':cache_change']
+ }
+ ],
+ ['LineEdit', ':cache_show',
+ { 'ro': True,
+ 'tt': _("The path to the (host's) package cache")
+ }
+ ],
+ ['Button', ':cache_change',
+ { 'text': _("Change"),
+ 'tt': _("Change the package cache path")
+ },
+ 'clicked'
+ ],
+
+ ['Button', ':install',
+ { 'text': _("Install"),
+ 'tt': _("This will start the installation to the set path")
+ },
+ 'clicked'
+ ],
+
+ ['DATA', 'install_page_data',
+ { 'messages':
+ { 'edit_pc': _("Editing pacman.conf options only"),
+ 'edit_pr': _("Editing pacman repositories"),
+ 'edit_mli': _("Editing mirror list for installation"),
+ 'prompt_ncp': _("Enter new package cache path:"),
+ 'edit_pri': _("Editing pacman repositories for installation"),
+ 'msg_pu': _("Package to add/update"),
+ 'filter_pu': _("Packages"),
+ 'prompt_pi': _("Enter the names of packages to install -"
+ "\n separated by spaces:"),
+ 'prompt_pr': _("Enter the names of packages to remove -"
+ "\n separated by spaces:"),
+ }
+ },
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/page_larchify.uim b/build_tools/larch7/larch0/gui/layouts/page_larchify.uim
new file mode 100644
index 0000000..df7be8b
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_larchify.uim
@@ -0,0 +1,177 @@
+# page_larchify.uim - The layout for the larchify page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.02.07
+
+(lambda USERHEADERS:
+[
+ ['Page', ':page_larchify',
+ { 'layout':
+ ['VBOX',
+ ':larchify',
+ ':users',
+ 'HLINE',
+ ['HBOX', ':overlay', ':locales', ':rcconf'],
+ '*',
+ ':larchify_advanced',
+ 'HLINE',
+ ['HBOX', ':oldsquash', '*', ':build']
+ ]
+ }
+ ],
+ # - - - - The profile editing frame
+ ['Label', ':larchify',
+ { 'markup': ['', " *** ", ['strong', _("The system to be"
+ " compressed must be installed and ready.")], " *** "]
+ }
+ ],
+
+ ['Button', ':locales',
+ { 'text': _("Edit supported locales"),
+ 'tt': _("Edit the /etc/locale.gen file to select"
+ " supported glibc locales")
+ },
+ 'clicked'
+ ],
+ ['Button', ':rcconf',
+ { 'text': _("Edit Arch configuration"),
+ 'tt': _("Edit the /etc/rc.conf file to configure the"
+ " live system")
+ },
+ 'clicked'
+ ],
+ ['Button', ':overlay',
+ { 'text': _("Edit overlay"),
+ 'tt': _("Open a file browser on the profile's 'rootoverlay'")
+ },
+ 'clicked'
+ ],
+
+ ['OptionalFrame', ':larchify_advanced',
+ { 'text': _("Advanced Options"),
+ 'layout':
+ ['HBOX', ':initcpio', '*', ':oldlocales', ':ssh']
+ }
+ ],
+ ['Button', ':initcpio',
+ { 'text': _("Edit mkinitcpio.conf"),
+ 'tt': _("Edit the configuration file for generating"
+ " the initramfs via mkinitcpio")
+ },
+ 'clicked'
+ ],
+ ['CheckBox', ':ssh',
+ { 'text': _("Generate ssh keys"),
+ 'tt': _("The ssh host keys will be pre-generated")
+ },
+ 'toggled'
+ ],
+ ['CheckBox', ':oldlocales',
+ { 'text': _("Reuse existing locales"),
+ 'tt': _("To save time it may be possible to reuse glibc"
+ " locales from a previous run")
+ },
+# 'toggled'
+ ],
+
+ ['CheckBox', ':oldsquash',
+ { 'text': _("Reuse existing system.sqf"),
+ 'tt': _("Reuse existing system.sqf, to save time if the"
+ " base system hasn't changed")
+ },
+# 'toggled'
+ ],
+ ['Button', ':build',
+ { 'text': _("Larchify"),
+ 'tt': _("Build the main components of the larch system")
+ },
+ 'clicked'
+ ],
+
+#Note that this should be disabled if installation directory is '/'
+ ['Frame', ':users',
+ { 'text': _("User accounts"),
+ 'layout':
+ ['VBOX',
+ ':utable',
+ ['HBOX', ':useradd', ':userdel', '*',
+ ':rootpwl', ':rootpwe', ':rootpwb'
+ ]
+ ]
+ }
+ ],
+ ['List', ':utable',
+ { 'selectionmode': 'Single',
+ 'headers': USERHEADERS,
+ 'compact': True,
+ 'tt': _("Click on a row to select, click on a selected"
+ " cell to edit")
+ },
+# 'select',
+ 'clicked'
+ ],
+ ['Button', ':useradd',
+ { 'text': _("Add user"),
+ 'tt': _("Create a new user-name")
+ },
+ 'clicked'
+ ],
+ ['Button', ':userdel',
+ { 'text': _("Delete user"),
+ 'tt': _("Remove the selected user-name")
+ },
+ 'clicked'
+ ],
+ ['Label', ':rootpwl',
+ { 'text': _("Root password:")
+ }
+ ],
+ ['LineEdit', ':rootpwe',
+ { 'ro': True,
+ 'tt': _("The unencrypted root password for the live system")
+ }
+ ],
+ ['Button', ':rootpwb',
+ { 'text': _("Change"),
+ 'tt': _("Enter a new password for the 'root' user")
+ },
+ 'clicked'
+ ],
+
+
+ ['DATA', 'larchify_page_data',
+ { 'messages':
+ { 'uheaders': USERHEADERS,
+ 'rn_error': _("Renaming failed, see log"),
+ 'ud_error': _("Couldn't adjust user definition"),
+ 'def_skel': _("Default (/etc/skel)"),
+ 'skel_lbl': _("This folder will be copied\n"
+ "to build the user's home folder:"),
+ 'skel_ttl': _("Choose 'skel' Folder"),
+ 'newlogin': _("Enter login-name for new user:"),
+ 'newrootpw': _("Enter root password for live system:"),
+ }
+ },
+ ],
+]
+)([_("User-Name"), _("Password"), _("Group"),
+ "UID", _("'skel' directory"),
+ _("Additional Groups"), _("Expert options")])
diff --git a/build_tools/larch7/larch0/gui/layouts/page_main.uim b/build_tools/larch7/larch0/gui/layouts/page_main.uim
new file mode 100644
index 0000000..3ab4a9e
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_main.uim
@@ -0,0 +1,101 @@
+# page_main.uim - The layout for the main window
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.06.26
+
+[
+ ['Window', ':larch',
+ { 'title': 'larch', 'size': '640_480',
+ 'icon': 'images/larchicon.png',
+ 'closesignal': '$$$uiclose$$$',
+ 'layout':
+ ['VBOX',
+ ['HBOX',
+ ':image',
+ ['VBOX',
+ ['HBOX', ':header', '*'],
+ ['HBOX', ':showlog', ':docs', '*', ':quit'],
+ ]
+ ],
+ ':tabs'
+ ]
+
+ }
+ ],
+
+ # - Header
+ ['Label', ':image',
+ { 'image': 'images/larch80.png'
+ }
+ ],
+ ['Label', ':header',
+ { 'markup': ['h1', ['color', '#c55500', ['em', 'larch '],
+ _("Live Arch Linux Construction Kit")]]
+ }
+ ],
+ ['Button', ':showlog',
+ { 'text': _("View Log"),
+ 'tt': _("This button switches to the log viewer"),
+ },
+ 'clicked'
+ ],
+ ['Button', ':docs',
+ { 'text': _("Help"),
+ 'tt': _("This button switches to the documentation viewer"),
+ },
+ 'clicked'
+ ],
+ ['Button', ':quit',
+ { 'text': _("Quit"),
+ 'tt': _("Stop the current action and quit the program"),
+ 'clicked': '$$$uiquit$$$'
+ },
+ ],
+
+#TODO
+ # - Main widget
+ ['Stack', ':tabs',
+ { 'pages': [':notebook', 'progress:page', 'log:page',
+ 'doc:page', 'edit:page']
+ }
+ ],
+
+ # - - The main page of the Stack
+ ['Notebook', ':notebook',
+ { 'tabs': [
+ [':page_settings', _("Project Settings")],
+ [':page_installation', _("Installation")],
+ [':page_larchify', _("Larchify")],
+ [':page_image', _("Medium Profile")],
+ [':page_medium', _("Make Medium")],
+ ]
+ },
+ 'changed'
+ ],
+
+ ['DATA', 'main_page_data',
+ { 'messages':
+ { 'authfail': _("Authentication failure"),
+ 'getpw': _("Enter the password to run as administrator:"),
+ }
+ },
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/page_medium.uim b/build_tools/larch7/larch0/gui/layouts/page_medium.uim
new file mode 100644
index 0000000..713a3c0
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_medium.uim
@@ -0,0 +1,271 @@
+# page_medium.uim - The layout for the medium building page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.12
+
+[
+ ['Page', ':page_medium',
+ { 'layout':
+ ['VBOX',
+ ['HBOX', ':source', 'VLINE', ':bootloader'],
+ 'HLINE',
+ ['HBOX', ':destination','VLINE', ':detection'],
+ ['HBOX', '*', ':vlabell', ':vlabele',
+ ':vlabelb'],
+ 'HLINE',
+ ['HBOX', ':chroot', 'VLINE,10',
+ ':bootcd', '*', ':make_medium']
+ ]
+ }
+ ],
+# Select source:
+# larch installation (default, but may be invalid), iso, cd-drive, partition
+ ['Frame', ':source',
+ { 'text': _("Select larch source"),
+ 'layout':
+ ['VBOX',
+ ['HBOX', ':source_larch', ':source_dev',
+ ':source_iso', ':source_path'],
+ ['HBOX', ':source_show', ':source_select']
+ ]
+ }
+ ],
+ ['RadioButton', ':source_larch',
+ { 'text': _("larchified system"),
+ 'tt': _("Use the system prepared within the larch build"
+ " directory")
+ },
+ 'toggled'
+ ],
+ ['RadioButton', ':source_dev',
+ { 'text': _("Device"),
+ 'tt': _("Use a system on a mountable device")
+ },
+ 'toggled'
+ ],
+ ['RadioButton', ':source_iso',
+ { 'text': _("'iso' file"),
+ 'tt': _("Use a system on an 'iso' file")
+ },
+ 'toggled'
+ ],
+ ['RadioButton', ':source_path',
+ { 'text': _("Path"),
+ 'tt': _("Use a directory within the filesystem")
+ },
+ 'toggled'
+ ],
+ ['LineEdit', ':source_show',
+ { 'ro': True,
+ 'tt': _("The location from where the larch system will"
+ " be fetched")
+ }
+ ],
+ ['Button', ':source_select',
+ { 'text': _("Choose"),
+ 'tt': _("Select the source location")
+ },
+ 'clicked'
+ ],
+
+#++++
+ ['OptionalFrame', ':destination',
+ { 'text': _("Write to partition"),
+ 'tt': _("Don't create an 'iso' (CD/DVD), write the larch"
+ " system to a partition (e.g. USB-stick)"),
+ 'layout':
+ ['VBOX',
+ ['HBOX', ':lm2', ':larchpart', ':selectpart'],
+ ['GRID', ['+', ':noformat', ':nombr'],
+ ['+', ':nolarchboot', ':dosave']]
+ ]
+ },
+ 'toggled'
+ ],
+ ['Label', ':lm2',
+ { 'text': _("Partition:")
+ }
+ ],
+ ['LineEdit', ':larchpart',
+ { 'ro': True,
+ 'tt': _("The partition to which the larch system is to"
+ " be installed")
+ }
+ ],
+ ['Button', ':selectpart',
+ { 'text': _("Choose"),
+ 'tt': _("Select the partition to receive the larch system")
+ },
+ 'clicked'
+ ],
+ ['CheckBox', ':noformat',
+ { 'text': _("Don't format"),
+ 'tt': _("Copy the data to the partition without formatting"
+ " first\n(not the normal procedure, NOT RECOMMENDED!)")
+ },
+# 'toggled'
+ ],
+ ['CheckBox', ':nombr',
+ { 'text': _("Don't install the bootloader"),
+ 'tt': _("The bootloader will not be installed, leaving"
+ " the mbr untouched\n"
+ "(you'll need to provide some other means of booting)")
+ },
+# 'toggled'
+ ],
+ ['CheckBox', ':nolarchboot',
+ { 'text': _("Not bootable via search"),
+ 'tt': _("Don't create the file 'larch/larchboot':\n"
+ " the medium will only be bootable by uuid, label"
+ " or partition name")
+ },
+ 'toggled'
+ ],
+ ['CheckBox', ':dosave',
+ { 'text': _("Enable session-saving"),
+ 'tt': _("Can override profile's 'larch/nosave' file,"
+ " to make session-saving possible in that case too")
+ },
+# 'toggled'
+ ],
+#----
+
+#++++++++
+ ['Frame', ':detection',
+ { 'text': _("Medium Detection"),
+ 'tt': _("Choose how the boot scripts determine where to"
+ " look for the larch system (ONLY ON PARTITIONED MEDIA)"),
+ 'layout':
+ ['VBOX', ':device', ':uuid', ':label', ':search']
+ }
+ ],
+ ['RadioButton', ':uuid',
+ { 'text': _("UUID"),
+ 'tt': _("Use the partition's UUID to find it")
+ },
+# 'toggled'
+ ],
+ ['RadioButton', ':label',
+ { 'text': _("LABEL"),
+ 'tt': _("Use the partition's label to find it")
+ },
+# 'toggled'
+ ],
+ ['RadioButton', ':device',
+ { 'text': _("Partition"),
+ 'tt': _("Use the partition's name (/dev/sdb1, etc.) to find it")
+ },
+# 'toggled'
+ ],
+ ['RadioButton', ':search',
+ { 'text': _("Search (for larchboot)"),
+ 'tt': _("Test all CD/DVD devices and partitions until"
+ " the file 'larch/larchboot' is found")
+ },
+ 'toggled'
+ ],
+#--------
+
+#+
+ # Defaults to that of the source
+ ['Label', ':vlabell',
+ { 'text': _("Volume Label:")
+ }
+ ],
+ ['LineEdit', ':vlabele',
+ { 'ro': True,
+ 'tt': _("The length may not exceed 16 bytes,"
+ " 11 for vfat(syslinux)")
+ }
+ ],
+ ['Button', ':vlabelb',
+ { 'text': _("Change"),
+ 'tt': _("Enter a new label for the volume, empty to use default")
+ },
+ 'clicked'
+ ],
+#-
+
+#++++
+ ['Frame', ':bootloader',
+ { 'text': _("Bootloader"),
+ 'tt': _("You can choose between GRUB and"
+ " syslinux/isolinux as bootloader"),
+ 'layout':
+ ['VBOX', ':grub', ':syslinux']
+ }
+ ],
+ ['RadioButton', ':grub',
+ { 'text': "GRUB",
+ 'tt': _("Use GRUB as bootloader")
+ },
+ 'toggled'
+ ],
+ ['RadioButton', ':syslinux',
+ { 'text': "syslinux/isolinux",
+ 'tt': _("Use syslinux (partition) or isolinux (CD/DVD)"
+ " as bootloader")
+ },
+ 'toggled'
+ ],
+#----
+
+ ['CheckBox', ':chroot',
+ { 'text': _("Use chroot"),
+ 'tt': _("Use the larch installation for the build process\n"
+ " - the default should be alright in most cases")
+ },
+# 'toggled'
+ ],
+
+ ['Button', ':bootcd',
+ { 'text': _("Create boot iso"),
+ 'tt': _("Create a small boot iso for this device (for"
+ " machines that can't boot from USB)")
+ },
+ 'clicked'
+ ],
+ ['Button', ':make_medium',
+ { 'text': _("Write the larch medium"),
+ 'tt': _("The larch image will be written to the 'iso' file"
+ " or to the partition, as selected")
+ },
+ 'clicked'
+ ],
+
+ ['DATA', 'medium_page_data',
+ { 'messages':
+ { 'medium_src': _("Select source medium folder"),
+ 'iso_src': _("Select source iso file"),
+ 'iso_type': _("larch iso images"),
+ 'parts_t': _("Select unmounted partition"),
+ 'parts_src': _("larch system source:"),
+ 'parts_dst': _("Device to receive larch system.\n"
+ "WARNING: Be very careful in choosing here,\n"
+ "if you choose the wrong one you might\n"
+ "seriously damage your system!"),
+ 'msg_med': _("Invalid larch medium folder: %s"),
+ 'prompt_label': _("Volume label (clear to use default):"),
+ 'isopath': _("Save 'iso' to ..."),
+ }
+ },
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/page_mediumprofile.uim b/build_tools/larch7/larch0/gui/layouts/page_mediumprofile.uim
new file mode 100644
index 0000000..f50a20a
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_mediumprofile.uim
@@ -0,0 +1,99 @@
+# page_mediumprofile.uim - The layout for the medium profile settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.08
+
+[
+ ['Page', ':page_image',
+ { 'layout':
+ ['VBOX',
+ ':bootlines',
+ ':grubtemplate',
+ ':syslinuxtemplate',
+ ':cdroot',
+ '*',
+ ['HBOX', ':nosessionsave', '*',
+# ':plabell', ':plabele', ':plabelb'
+ ]
+ ]
+ }
+ ],
+ ['Button', ':bootlines',
+ { 'text': _("Edit boot entries"),
+ 'tt': _("Edit the file determining the boot entries")
+ },
+ 'clicked'
+ ],
+ ['Button', ':grubtemplate',
+ { 'text': _("Edit grub template"),
+ 'tt': _("Edit grub's configuration file,"
+ " but not the larch boot entries")
+ },
+ 'clicked'
+ ],
+ ['Button', ':syslinuxtemplate',
+ { 'text': _("Edit syslinux/isolinux template"),
+ 'tt': _("Edit the syslinux/isolinux configuration file,"
+ " but not the larch boot entries")
+ },
+ 'clicked'
+ ],
+ ['Button', ':cdroot',
+ { 'text': _("Edit cd-root (open in file browser)"),
+ 'tt': _("Open a file browser on the profile's 'cd-root'"
+ " folder")
+ },
+ 'clicked'
+ ],
+# ['Label', ':plabell',
+# { 'text': _("Volume Label:")
+# }
+# ],
+# ['LineEdit', ':plabele',
+# { 'ro': True,
+# 'tt': _("The length may not exceed 16 bytes,"
+# " 11 for vfat(syslinux)")
+# }
+# ],
+# ['Button', ':plabelb',
+# { 'text': _("Change"),
+# 'tt': _("Enter a new label for the volume")
+# },
+# 'clicked'
+# ],
+ ['CheckBox', ':nosessionsave',
+ { 'text': _("Disable session saving"),
+ 'tt': _("If checked, the medium will have the file"
+ " 'larch/nosave',\n"
+ "which only has an effect on writable media.")
+ },
+ 'toggled'
+ ],
+
+
+#?
+# ['DATA', 'mediumprofile_page_data',
+# { 'messages':
+# { 'blah': _("Blah blah"),
+# }
+# },
+# ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/page_project.uim b/build_tools/larch7/larch0/gui/layouts/page_project.uim
new file mode 100644
index 0000000..938c646
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/page_project.uim
@@ -0,0 +1,181 @@
+# page_project.uim - The layout for the project settings page
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.06.27
+
+[
+ ['Page', ':page_settings',
+ { 'layout':
+ ['VBOX',
+ ':settings_profile',
+ '*',
+ ':options_advanced',
+ '*'
+ ]
+ }
+ ],
+ # - - - - The profile selection frame
+ ['Frame', ':settings_profile',
+ { 'text': _("Profile"),
+ 'layout':
+ ['VBOX',
+ ['HBOX', ':choose_profile', ':choose_profile_combo',
+ '*', ':profile_browse'],
+ ['HBOX', ':profile_rename', ':profile_delete',
+ ':profile_save']
+ ]
+ }
+ ],
+ ['Label', ':choose_profile',
+ { 'text': _("Select:"),
+ 'align': 'right'
+ }
+ ],
+ ['ComboBox', ':choose_profile_combo',
+ { 'width': 200,
+ 'tt': _("Choose a profile from those already in your"
+ " larch working folder")
+ },
+ 'changed'
+ ],
+ ['Button', ':profile_browse',
+ { 'text': _("Browse for Profile"),
+ 'tt': _("Fetch a profile from the file-system")
+ },
+ 'clicked'
+ ],
+ ['Button', ':profile_rename',
+ { 'text': _("Rename"),
+ 'tt': _("Rename the current profile")
+ },
+ 'clicked'
+ ],
+ ['Button', ':profile_delete',
+ { 'text': _("Delete"),
+ 'tt': _("Delete an unused profile")
+ },
+ 'clicked'
+ ],
+ ['Button', ':profile_save',
+ { 'text': _("Copy to ..."),
+ 'tt': _("Copy the current profile to somehere else")
+ },
+ 'clicked'
+ ],
+
+ # - - - - Advanced project options
+ ['OptionalFrame', ':options_advanced',
+ { 'text': _("Advanced Project Options"),
+ 'layout':
+ ['HBOX',
+ #['HBOX', '*', ':lplat', ':platform'],
+ ['GRID',
+ ['+', ':choose_project', ':choose_project_combo'],
+ ['+', ':new_project', ':project_delete']
+ ],
+ 'VLINE,3',
+ ':installation_path'
+ ]
+ }
+ ],
+# Pending better support in Arch/pacman
+# ['Label', '::lplat',
+# { 'text': _("Platform (processor architecture):")
+# }
+# ],
+# ['ComboBox', ':platform',
+# { 'tt': _("Which processor architecture?")
+# },
+# 'changed'
+# ],
+ ['Label', ':choose_project',
+ { 'text': _("Choose Existing Project:")
+ }
+ ],
+ ['ComboBox', ':choose_project_combo',
+ { 'tt': _("Choose a project from those already defined"),
+ 'width': 120
+ },
+ 'changed'
+ ],
+ ['Button', ':new_project',
+ { 'text': _("New Project"),
+ 'tt': _("Create a new project")
+ },
+ 'clicked'
+ ],
+ ['Button', ':project_delete',
+ { 'text': _("Delete"),
+ 'tt': _("Delete a project")
+ },
+ 'clicked'
+ ],
+ ['Frame', ':installation_path',
+ { 'text': _("Installation Path"),
+ 'layout':
+ ['HBOX', ':installation_path_show',
+ ':installation_path_change']
+ }
+ ],
+ ['LineEdit', ':installation_path_show',
+ { 'ro': True,
+ 'tt': _("The root directory of the Arch installation"
+ " to larchify")
+ }
+ ],
+ ['Button', ':installation_path_change',
+ { 'text': _("Change"),
+ 'tt': _("Change the root directory of the Arch installation")
+ },
+ 'clicked'
+ ],
+
+ ['DATA', 'project_page_data',
+ { 'messages':
+ { 'file_ps': _("Select profile source folder"),
+ 'prompt_pr': _("Destination profile exists - replace it?"),
+ 'prompt_pn': _("Enter new name for current profile:"),
+ 'prompt_pe': _("Profile '%s' exists already"),
+ 'msg_pu': _("Can't rename the profile,"
+ " it is in use by other projects"),
+ 'file_sp': _("Save profile folder"),
+ 'prompt_dr': _("Destination exists - replace it?"),
+ 'prompt_dp': _("Select the profile for deletion"),
+ 'delprof': _("Remove Profile"),
+ 'msg_npf': _("There are no profiles which can"
+ " be deleted - all are in use"),
+ 'msg_dpff': _("Couldn't delete profile '%s' -"
+ " check permissions"),
+ 'prompt_ip': _("An empty path here will reset to the default.\n"
+ " WARNING: Double check your path -\n"
+ " If you make a mistake here it could destroy your system!"
+ "\n\nEnter new installation path:"),
+ 'prompt_np': _("Enter name for new project:"),
+ 'msg_pe': _("Project '%s' already exists"),
+ 'prompt_pd': _("Select the project for deletion"),
+ 'delproj': _("Remove Project"),
+ 'msg_np': _("There are no projects which can be deleted"),
+ 'msg_npd': _("'%s' is not a profile folder"),
+ 'msg_piu': _("The path '%s' is already in use, not saving"),
+ }
+ },
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/layouts/progress.uim b/build_tools/larch7/larch0/gui/layouts/progress.uim
new file mode 100644
index 0000000..62ae9e2
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/layouts/progress.uim
@@ -0,0 +1,64 @@
+# progress.uim - The layout for the progress widget
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.05.22
+
+[
+ ['Frame', 'progress:page',
+ { 'layout':
+ ['VBOX',
+ 'progress:header',
+ ['HBOX',
+ 'progress:text',
+ ['VBOX', 'progress:cancel', '*', 'progress:done']
+ ],
+ 'progress:progress'
+ ]
+ }
+ ],
+ ['Label', 'progress:header',
+ { 'markup': ['', ['h2', _("Processing ...")],
+ ['p', _("Here you can follow the detailed, low-level"
+ " progress of the commands.")]]
+ }
+ ],
+ ['TextEdit', 'progress:text',
+ { 'ro': True
+ }
+ ],
+ ['LineEdit', 'progress:progress',
+ { 'ro': True,
+ 'tt': _("An indication of the progress of the current"
+ " operation, if possible")
+ }
+ ],
+ ['Button', 'progress:cancel',
+ { 'text': _("Cancel"),
+ 'tt': _("Stop the current action"),
+ 'clicked': '$$$cancel$$$'
+ }
+ ],
+ ['Button', 'progress:done',
+ { 'text': _("Done"),
+ 'clicked': ''
+ }
+ ],
+]
diff --git a/build_tools/larch7/larch0/gui/project.py b/build_tools/larch7/larch0/gui/project.py
new file mode 100644
index 0000000..f01e578
--- /dev/null
+++ b/build_tools/larch7/larch0/gui/project.py
@@ -0,0 +1,428 @@
+# project.py - Project management
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch 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.
+#
+# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.14
+
+from config import *
+import os, shutil, pwd
+from glob import glob
+from subprocess import call
+from userinfo import Userinfo
+
+CONFIG_DIR = '.config/larch' # within the user's home directory
+APP_CONF = 'app.conf' # within larch config directory
+PROJECT_CONF = 'project.conf' # within project directory
+PROJECT0 = 'larch-0' # default project
+PROFILE0 = 'default' # default profile
+
+# Some default values for the project config file
+DEFAULTS = { 'installation_dir' : '',
+ 'pacman_cache' : '/var/cache/pacman/pkg',
+ 'profile' : PROFILE0,
+ 'profile_browse_dir': '', # => use default
+ 'installrepo' : '',
+ 'medium_iso' : 'Yes', # 'Yes' | ''
+ 'medium_btldr' : 'syslinux', # 'grub' | 'syslinux'
+ 'medium_search' : 'search', # 'search' | 'uuid' | 'label' | 'device'
+ 'medium_label' : '', # => fetch default
+ 'isosavedir' : '',
+ 'isofile' : '',
+ 'bootisofile' : '',
+ 'bootisolabel' : '',
+ }
+
+
+# Default values for the application config file
+APP_DEFAULTS = {
+ 'project' : PROJECT0,
+ 'filebrowser' : 'xdg-open $',
+ }
+
+
+
+class ProjectManager:
+ def __init__(self):
+ add_exports( (
+ ('getitem', self.getitem),
+ ('getbool', self.getbool),
+ ('setitem', self.setitem),
+ ('setbool', self.setbool),
+ ('get_projects', self.list_projects),
+ ('get_profiles', self.list_profiles),
+ ('get_installation_dir', self.get_ipath),
+ ('set_installation_dir', self.set_ipath),
+ ('testmedium', self.testmedium),
+ ('set_project', self.set_projectname),
+ ('get_project', self.get_projectname),
+ ('delete_project', self.delete_project),
+ ('delete_profile', self.delete_profile),
+ ('list_free_projects', self.list_free_projects),
+ ('list_free_profiles', self.list_free_profiles),
+ ('get_new_profile', self.get_new_profile),
+ ('rename_profile', self.rename_profile),
+ ('can_rename_profile', self.can_rename_profile),
+ ('save_profile', self.save_profile),
+ ('get_profile', self.get_profile),
+ ('set_profile', self.set_profile),
+ ('get_profile_bookmarks', self.get_profile_bookmarks),
+ ('get_mediumlabel', self.get_mediumlabel),
+ ('set_mediumlabel', self.set_mediumlabel),
+ ('getisosavedir', self.getisosavedir),
+ ('getisofile', self.getisofile),
+ ('getbootisofile', self.getbootisofile),
+ ('getbootisolabel', self.getbootisolabel),
+ ('newUserinfo', self.newUserinfo),
+ ('allusers', self.allusers),
+ ('getuserinfo', self.getuserinfo),
+ ('newuser', self.newuser),
+ ('userset', self.userset),
+ ('deluser', self.deluser),
+ ('listskels', self.listskels),
+ ('saveusers', self.saveusers))
+ )
+
+
+ def init(self):
+ self.projects_base = os.path.join(os.environ['HOME'], CONFIG_DIR)
+ self.profiles_dir = os.path.join(self.projects_base, 'myprofiles')
+ # Ensure the presence of the larch default project folder
+ dpf = '%s/p_%s' % (self.projects_base, PROJECT0)
+ if not os.path.isdir(dpf):
+ os.makedirs(dpf)
+ # Ensure the presence of the profiles folder and the 'default' profile
+ if not os.path.isdir(self.profiles_dir):
+ os.mkdir(self.profiles_dir)
+ self.default_profile_dir = os.path.join(self.profiles_dir, PROFILE0)
+ if not os.path.isdir(self.default_profile_dir):
+ call(['cp', '-a', base_dir + '/profiles/'+ PROFILE0,
+ self.profiles_dir])
+
+ # The application configs
+ self.aconfig_file = os.path.join(self.projects_base, APP_CONF)
+ self.aconfig = self.getconfig(self.aconfig_file)
+
+ # The project-specific configs
+ self.set_projectname(self.appget('project'))
+
+
+ def get_projectname(self):
+ return (True, self.project_name)
+
+ def set_projectname(self, name):
+ self.project_dir = os.path.join(self.projects_base, 'p_' + name)
+ plist = self.list_projects()[1]
+ if name not in plist:
+ os.mkdir(self.project_dir)
+
+ self.pconfig_file = os.path.join(self.project_dir, PROJECT_CONF)
+ self.pconfig = self.getconfig(self.pconfig_file)
+ self.profile_name = self.get('profile')
+
+ self.profile_path = os.path.join(self.profiles_dir, self.profile_name)
+ self.appset('project', name)
+ self.project_name = name
+ return (True, None)
+
+ def delete_project(self, name):
+ # This should probably be run as root, in case the build directory
+ # is inside it ... cross that bridge when we come to it!
+ r = call(['rm', '-r', '--interactive=never',
+ os.path.join(self.projects_base, 'p_' + name)])
+ return (True, r == 0)
+
+
+ def delete_profile(self, name):
+ r = call(['rm', '-r', '--interactive=never',
+ os.path.join(self.profiles_dir, name)])
+ return (True, r == 0)
+
+
+ def get_profile(self):
+ return (True, self.profile_name)
+
+ def set_profile(self, name):
+ self.set('profile', name)
+ self.profile_name = name
+ self.profile_path = os.path.join(self.profiles_dir, self.profile_name)
+ return (True, None)
+
+
+ def rename_profile(self, name):
+ os.rename(self.profile_path, os.path.join(self.profiles_dir, name))
+ self.set_profile(name)
+ return (True, None)
+
+
+ def get_new_profile(self, src):
+ if not os.path.isfile(src + '/addedpacks'):
+ return (True, False)
+ pname = os.path.basename(src)
+ dst = os.path.join(self.profiles_dir, pname)
+ call(['rm', '-rf', dst])
+ shutil.copytree(src, dst)
+ self.set_profile(pname)
+ return (True, True)
+
+
+ def get_profile_bookmarks(self):
+ return (True, ((self.projects_base + '/myprofiles',
+ _("Working Profiles")),
+ (base_dir + '/profiles', _("Examples")),
+ ('/', _("File-system"))
+ ))
+
+
+# What about not allowing changes to the default profile?
+# That would mean also no renaming?
+# One would have to copy a profile into the project before going
+# any further ...
+# Is it right to share profiles between projects? (Probably)
+
+#+++++++++++++++++++++++++++++++++++++++++++++++
+### A very simple configuration file handler
+ def getconfig(self, filepath):
+ cfg = {}
+ if os.path.isfile(filepath):
+ fh = open(filepath)
+ for line in fh:
+ ls = line.split('=', 1)
+ if len(ls) > 1:
+ cfg[ls[0].strip()] = ls[1].strip()
+ return cfg
+
+
+ def saveconfig(self, filepath, config):
+ fh = open(filepath, 'w')
+ ci = config.items()
+ ci.sort()
+ for kv in ci:
+ fh.write('%s = %s\n' % kv)
+ fh.close()
+###
+#-----------------------------------------------
+
+ def list_projects(self):
+ projects = [p[2:] for p in os.listdir(self.projects_base)
+ if p.startswith('p_')]
+ projects.sort()
+ return (True, projects)
+
+
+ def list_free_projects(self):
+ """This returns a list of projects which are free for (e.g.) deletion.
+ """
+ plist = self.list_projects()[1]
+ plist.remove(PROJECT0) # this one is not 'free'
+ if self.project_name in plist:
+ plist.remove(self.project_name)
+ return (True, plist)
+
+
+ def list_profiles(self):
+ profiles = [d for d in os.listdir(self.profiles_dir) if
+ os.path.isfile(os.path.join(self.profiles_dir, d, 'addedpacks'))]
+ profiles.sort()
+ return (True, profiles)
+
+
+ def list_free_profiles(self):
+ """This returns a list of profiles which are not in use by any project.
+ """
+ plist = self.list_profiles()[1]
+ plist.remove(PROFILE0) # this one is not 'free'
+ for project in self.list_projects()[1]:
+ cfg = self.getconfig(os.path.join(self.projects_base,
+ 'p_' + project, PROJECT_CONF))
+ p = cfg.get('profile')
+ if p in plist:
+ plist.remove(p)
+ return (True, plist)
+
+
+ def can_rename_profile(self):
+ if self.profile_name == PROFILE0:
+ return (True, False)
+ for project in self.list_projects()[1]:
+ if project != self.project_name:
+ cfg = self.getconfig(os.path.join(self.projects_base,
+ 'p_' + project, PROJECT_CONF))
+ if self.profile_name == cfg.get('profile'):
+ return (True, False)
+ return (True, True)
+
+
+ def save_profile(self, path, force):
+ #path = os.path.join(path, self.profile_name)
+ if os.path.exists(path):
+ if force:
+ call(['rm', '-rf', path])
+ elif os.path.isfile(os.path.join(path, addedpacks)):
+ # This is an existing profile
+ return (True, False)
+ else:
+ # This location is otherwise in use
+ return (True, None)
+ shutil.copytree(self.profile_path, path)
+ return (True, True)
+
+
+ def appget(self, item):
+ """Read an entry in the application configuration.
+ """
+ v = self.aconfig.get(item)
+ if v:
+ return v
+ elif APP_DEFAULTS.has_key(item):
+ return APP_DEFAULTS[item]
+ debug("Unknown application configuration option: %s" % item)
+ assert False
+
+ def appset(self, item, value):
+ """Set an entry in the application configuration.
+ """
+ self.aconfig[item] = value.strip()
+ self.saveconfig(self.aconfig_file, self.aconfig)
+
+
+ def getitem(self, item):
+ return (True, self.get(item))
+
+ def getbool(self, item):
+ return (True, bool(self.get(item)))
+
+ def setitem(self, item, value):
+ self.set(item, value)
+ return (True, None)
+
+ def setbool(self, item, on):
+ self.set(item, 'Yes' if on else '')
+ return (True, None)
+
+
+ def get(self, item):
+ """Read an entry in the project configuration.
+ """
+ v = self.pconfig.get(item)
+ if v:
+ return v
+ elif DEFAULTS.has_key(item):
+ return DEFAULTS[item]
+ debug("Unknown configuration option: %s" % item)
+ assert False
+
+ def set(self, item, value):
+ """Set an entry in the project configuration.
+ """
+ self.pconfig[item] = value.strip()
+ self.saveconfig(self.pconfig_file, self.pconfig)
+# return True
+
+
+ def get_ipath(self):
+ ip = self.get('installation_dir')
+ if not ip:
+ ip = self.set_ipath('')[1]
+ return (True, ip)
+
+ def set_ipath(self, path):
+ path = path.strip()
+ if path:
+ path = '/' + path.strip('/')
+ else:
+ path = os.environ['HOME'] + '/larch_build'
+ self.set('installation_dir', path)
+ return (True, path)
+
+
+ def testmedium(self):
+ ipath = self.get_ipath()[1]
+ path = ipath + CHROOT_DIR_MEDIUM
+ bl = []
+ nosave = False
+ ok = os.path.isfile(path + '/larch/system.sqf')
+ if ok:
+ if os.path.isfile(ipath + GRUBDIR + '/stage2_eltorito'):
+ bl.append('grub')
+ if os.path.isfile(ipath + SYSLINUXDIR + '/isolinux.bin'):
+ bl.append('syslinux')
+ if os.path.isfile(self.profile_path + '/nosave'):
+ nosave = True
+ return (True, (path, ok, bl, nosave))
+
+
+ def newUserinfo(self):
+ self.userInfo = Userinfo(self.profile_path)
+ return (True, None)
+
+ def allusers(self):
+ return (True, self.userInfo.allusers())
+
+ def getuserinfo(self, user, fields):
+ return (True, self.userInfo.userinfo(user, fields))
+
+ def newuser(self, user):
+ return (True, self.userInfo.newuser(user))
+
+ def saveusers(self):
+ return (True, self.userInfo.saveusers())
+
+ def userset(self, uname, field, text):
+ self.userInfo.userset(uname, field, text)
+ return (True, None)
+
+ def deluser(self, user):
+ return (True, self.userInfo.deluser(user))
+
+ def listskels(self):
+ return (True, glob(self.profile_path + '/skel_*'))
+
+
+ def get_mediumlabel(self):
+ l = self.get('medium_label')
+ return (True, l if l else LABEL)
+
+ def set_mediumlabel(self, l):
+ if len(l) > 16:
+ l = l[:16]
+ self.set('medium_label', l)
+ return self.get_mediumlabel()
+
+
+ def getisosavedir(self):
+ d = self.get('isosavedir')
+ return (True, d if d else os.environ['HOME'])
+
+ def getisofile(self):
+ f = self.get('isofile')
+ return (True, f if f else ISOFILE)
+
+ def getbootisofile(self):
+ f = self.get('bootisofile')
+ return (True, f if f else BOOTISO)
+
+ def getbootisolabel(self):
+ l = self.get('bootisolabel')
+ return (True, l if l else ISOLABEL)
+
+
+
+import __builtin__
+__builtin__.project_manager = ProjectManager()