diff options
Diffstat (limited to 'build_tools/l7/larch0/gui')
27 files changed, 5336 insertions, 0 deletions
| diff --git a/build_tools/l7/larch0/gui/MEDIUM_README b/build_tools/l7/larch0/gui/MEDIUM_README new file mode 100644 index 0000000..ede0975 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/askpass.py b/build_tools/l7/larch0/gui/askpass.py new file mode 100755 index 0000000..ac65325 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/controller.py b/build_tools/l7/larch0/gui/controller.py new file mode 100644 index 0000000..2025301 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/dirview.py b/build_tools/l7/larch0/gui/dirview.py new file mode 100755 index 0000000..303f9b6 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/dispatcher.py b/build_tools/l7/larch0/gui/dispatcher.py new file mode 100755 index 0000000..bcdc94c --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/docviewer.py b/build_tools/l7/larch0/gui/front/docviewer.py new file mode 100644 index 0000000..a0af083 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/editor.py b/build_tools/l7/larch0/gui/front/editor.py new file mode 100644 index 0000000..649f55c --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/logview.py b/build_tools/l7/larch0/gui/front/logview.py new file mode 100644 index 0000000..b9d2ac3 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/mainwindow.py b/build_tools/l7/larch0/gui/front/mainwindow.py new file mode 100644 index 0000000..407b46a --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/page_installation.py b/build_tools/l7/larch0/gui/front/page_installation.py new file mode 100644 index 0000000..111e247 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/page_larchify.py b/build_tools/l7/larch0/gui/front/page_larchify.py new file mode 100644 index 0000000..9868727 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/page_medium.py b/build_tools/l7/larch0/gui/front/page_medium.py new file mode 100644 index 0000000..75f0efe --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/page_mediumprofile.py b/build_tools/l7/larch0/gui/front/page_mediumprofile.py new file mode 100644 index 0000000..0ebb769 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/page_project.py b/build_tools/l7/larch0/gui/front/page_project.py new file mode 100644 index 0000000..e9b902d --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/front/uim.py b/build_tools/l7/larch0/gui/front/uim.py new file mode 100644 index 0000000..71e106b --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/larch.py b/build_tools/l7/larch0/gui/larch.py new file mode 100755 index 0000000..bddc78c --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/docviewer.uim b/build_tools/l7/larch0/gui/layouts/docviewer.uim new file mode 100644 index 0000000..8e85a0f --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/editor.uim b/build_tools/l7/larch0/gui/layouts/editor.uim new file mode 100644 index 0000000..2338f6e --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/logger.uim b/build_tools/l7/larch0/gui/layouts/logger.uim new file mode 100644 index 0000000..0ecb7bf --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_installation.uim b/build_tools/l7/larch0/gui/layouts/page_installation.uim new file mode 100644 index 0000000..0949dcd --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_larchify.uim b/build_tools/l7/larch0/gui/layouts/page_larchify.uim new file mode 100644 index 0000000..df7be8b --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_main.uim b/build_tools/l7/larch0/gui/layouts/page_main.uim new file mode 100644 index 0000000..3ab4a9e --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_medium.uim b/build_tools/l7/larch0/gui/layouts/page_medium.uim new file mode 100644 index 0000000..713a3c0 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_mediumprofile.uim b/build_tools/l7/larch0/gui/layouts/page_mediumprofile.uim new file mode 100644 index 0000000..f50a20a --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/page_project.uim b/build_tools/l7/larch0/gui/layouts/page_project.uim new file mode 100644 index 0000000..938c646 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/layouts/progress.uim b/build_tools/l7/larch0/gui/layouts/progress.uim new file mode 100644 index 0000000..62ae9e2 --- /dev/null +++ b/build_tools/l7/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/l7/larch0/gui/project.py b/build_tools/l7/larch0/gui/project.py new file mode 100644 index 0000000..f01e578 --- /dev/null +++ b/build_tools/l7/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() | 
