diff options
Diffstat (limited to 'build_tools/larch8/larch0/gui/front')
-rw-r--r-- | build_tools/larch8/larch0/gui/front/docviewer.py | 68 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/editor.py | 115 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/logview.py | 94 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/mainwindow.py | 205 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/page_installation.py | 193 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/page_larchify.py | 296 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/page_medium.py | 441 | ||||
-rw-r--r-- | build_tools/larch8/larch0/gui/front/page_project.py | 241 |
8 files changed, 1653 insertions, 0 deletions
diff --git a/build_tools/larch8/larch0/gui/front/docviewer.py b/build_tools/larch8/larch0/gui/front/docviewer.py new file mode 100644 index 0000000..de51605 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/docviewer.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python2 +# +# 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/larch8/larch0/gui/front/editor.py b/build_tools/larch8/larch0/gui/front/editor.py new file mode 100644 index 0000000..202f236 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/editor.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python2 +# +# 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/larch8/larch0/gui/front/logview.py b/build_tools/larch8/larch0/gui/front/logview.py new file mode 100644 index 0000000..7e506a5 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/logview.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python2 +# +# 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.08.10 + +import locale +# Try to work around problems when the system encoding is not utf8 +encoding = locale.getdefaultlocale()[1] + +#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 isinstance(line, str): + line = line.decode(encoding, 'replace') + 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 + if isinstance(line, str): + line = line.decode(encoding, 'replace') + ui.command('log:text.append_and_scroll', line) + + def _show(self): + ui.runningtab(2) + + def _hide(self): + ui.runningtab() diff --git a/build_tools/larch8/larch0/gui/front/mainwindow.py b/build_tools/larch8/larch0/gui/front/mainwindow.py new file mode 100644 index 0000000..8bf2a57 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/mainwindow.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python2 +# +# (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.10.12 + +import __builtin__ +from liblarch.suim import Suim, debug +__builtin__.debug = debug + + + +def larchcall(script, *args): + if script[0] != '*': + progress.start() # initialize progress widget + ui.setcb(None) + else: + # This script won't switch to the progress widget, but accepts + # a callback to handle the resulting output + script = script[1:] + ui.setcb(args[0]) + args = args[1:] + fss('larchscript', script, *args) + +__builtin__.larchcall = larchcall + + + +class Ui(Suim): + def __init__(self): + self.setcb(None) + Suim.__init__(self, 'larch', busywidgets=[':larch']) + + self.connect('$$$uiclose$$$', self.quit) + self.connect('$$$uiquit$$$', self.quit) + self.connect('$$$cancel$$$', self.interrupt) + self.progressing = False # used for managing progress display + + + def sigin(self, signal, *args): + self.idle_add(getattr(self, 'sig_' + signal), *args) + + + def setcb(self, cb): + """Set a callback to run when a 'larchscript' sends an output line, + or completes. + """ + self._read_cb = cb + + + def isbusy(self): + return self._read_cb != None + + + 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, filter=None): +# Actually this should access the file-system via 'fss' ... +# (that would require a new, custom widget) + if dirsonly: + return self.command('fileDialog_getdir', message, not create, startdir) + if file and startdir: + startdir = startdir + '/' + file + if create: + return self.command('fileDialog_save', message, startdir, filter) + return self.command('fileDialog_open', message, startdir, filter) + + def enable_installation_page(self, on): + self.command(':notebook.enableTab', 1, on) + + def interrupt(self): + fss('larchscript', 'interrupt') + + def quit(self): + """Do any tidying up which may be necessary. + """ + fss('larchscript', 'close') + Suim.quit(self) + + def data(self, key): + return self.command('main_page_data.get', key) + + + def sig_get_password(self, message): + """This is a callback, triggered by signal 'get_password' + to ask the user to input the password. + """ + fss('larchscript', 'sendpassword', *ui.command('textLineDialog', + message, "larch: pw", "", True)) + + + def sig_line_cb(self, message): + """A line has been received from the 'larchscript' (this is a callback, + 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 message.startswith('>-'): + if message.startswith('>-_$$_'): + # Informing us of the pid + fss('larchscript', 'pid', int(message.rsplit('_', 1)[1])) + else: + # It is a progress report + progress.set(message[2:]) + self.progressing = True + return + + else: + if self.progressing: + progress.set() + self.progressing = False + + progress.addLine(message) + if message.startswith('?>'): + # a query (yes/no): pop up the query + fss('larchscript', 'reply', '??YES' if ui.command( + 'confirmDialog', message[2:]) else '??NO') + elif self._read_cb: + self._read_cb(message) + + + def sig_end_cb(self, ok): + """A callback for the end of a 'larchscript'. + The completion code, ok, is ignored. + """ + if self._read_cb: + self._read_cb(None) + self._read_cb = None + self.busy(False) + else: + progress.end() + + + def sig_log(self, line): + logger.addLine(line) + + + + +def tab_changed(index): + __builtin__.stage = pages[index] + stage.enter() + +from page_project import ProjectSettings +from page_installation import Installation +from page_larchify import Larchify +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 = [] # Must be initialized before init() because of calls to tab_changed + +def init(): + pages.append(ProjectSettings()) + pages.append(Installation()) + pages.append(Larchify()) + 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(':notebook*changed', tab_changed) + + ui.command(':larch.pack') + # Set up the first gui page (project settings) + pages[0].setup() + ui.command(':larch.show') + diff --git a/build_tools/larch8/larch0/gui/front/page_installation.py b/build_tools/larch8/larch0/gui/front/page_installation.py new file mode 100644 index 0000000..82d57c5 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/page_installation.py @@ -0,0 +1,193 @@ +# 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.08.12 + +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), + (':updateall*clicked', self.doupdateall), + (':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 doupdateall(self): + self.archin('updateall') + + + 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. + """ + larchcall('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/larch8/larch0/gui/front/page_larchify.py b/build_tools/larch8/larch0/gui/front/page_larchify.py new file mode 100644 index 0000000..a63ecff --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/page_larchify.py @@ -0,0 +1,296 @@ +# 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.10.07 + +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), + (':kernelb*clicked', self.kernelfile), + (':kernelmkib*clicked', self.kernelpreset), + ) + + 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() + + # Kernel info + self.showkernel() + + 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.larch', + 'install:etc/mkinitcpio.conf.larch') + + + def overlay(self): + fss('browse', 'profile:rootoverlay') + + + def showkernel(self): + if fss('isfile', 'profile:kernel'): + ki = fss('readfile', 'profile:kernel') + else: + ki = fss('readfile', 'base:data/kernel') + self.kernel, self.kernelpreset = ki.split() + ui.command(':kernele.text', self.kernel) + ui.command(':kernelmkie.text', self.kernelpreset) + + + def kernelfile(self): + ok, kf = ui.command('textLineDialog', self.data('kernelf'), + "larchify", self.kernel) + if ok: + kf = kf.strip() + if not kf: + fss('rm_rf', 'profile:kernel') + + elif (' ' in kf) or not fss('isfile', 'install:boot/' + kf): + run_error(_("Invalid kernel binary: %s") % kf) + return + else: + fss('savefile', 'profile:kernel', kf + ' ' + self.kernelpreset) + self.showkernel() + + + def kernelpreset(self): + ok, kp = ui.command('textLineDialog', self.data('kernelp'), + "larchify", self.kernelpreset) + if ok: + kp = kp.strip() + if not kp: + fss('rm_rf', 'profile:kernel') + + elif (' ' in kp) or not fss('isfile', + 'install:etc/mkinitcpio.d/%s.preset' % kp): + run_error(_("Invalid kernel mkinitcpio preset: %s") % kp) + return + else: + fss('savefile', 'profile:kernel', self.kernel + ' ' + kp) + self.showkernel() + + + def build(self): + larchcall('larchify', ui.command(':oldsquash.active'), + ui.command(':oldlocales.active')) diff --git a/build_tools/larch8/larch0/gui/front/page_medium.py b/build_tools/larch8/larch0/gui/front/page_medium.py new file mode 100644 index 0000000..e63ce13 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/page_medium.py @@ -0,0 +1,441 @@ +# 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.11.28 + +"""This page takes a directory processed by larchify. It produces a bootable +larch medium, or, in the case of CD/DVD, an iso image. +It also handles creation of a boot-iso for an existing larch medium (partition) +and can copy larch media to different devices. +""" + +import os +from config import detection_methods, OKFS + + +class Medium: + def __init__(self): + self.medium = None + self.srcmedium = None + self.detectionmodes = detection_methods.split('|') + ui.widgetlist(fss('fetch_layout', 'page_medium.uim')) + + ui.connectlist( + (':vlabelb*clicked', self.newlabel), + (':selectpart*clicked', self.choosedest), + (':selectsrc*clicked', self.choosesrc), + (':persist*toggled', self.persistence), + (':ovl_journal*toggled', self.journal), + (':mediumtype*changed', self.mediumtype), + (':srctype*changed', self.srctype), + (':make_medium*clicked', self.make), + (':nolarchboot*toggled', self.bootnosearch), + (':detection*changed', self.mediumsearch), + (':pformat*toggled', self.setpformat), + (':bootlines*clicked', self.editbootlines), + (':syslinuxtemplate*clicked', self.editsyslin), + (':cdroot*clicked', self.browsecdroot), + ) + + + def enter(self): + """This is called when the page is entered/selected/shown. + It performs initialisations which depend on the state. + """ + self.destinationpath = '' + self.fsok = None + self.source, ok = fss('testlarchify') # check the larchified installation + if not ok: + run_error(self.data('msg_med') % self.source) + self.source = '' + self.mediumtype(None) + if self.medium != 'medium-boot': + self.srctype(None) + detect = fss('getitem', 'medium_search') + ui.command(':detection.set', + [self.data('detectionmodes')[l] for l in self.detectionmodes], + self.detectionmodes.index(detect)) + self.nlbenable(detect) + ui.command(':nolarchboot.set', fss('getbool', 'boot_nosearch')) + ui.command(':vlabele.text', fss('get_mediumlabel')) + ui.command(':ovl_journal.set', fss('getbool', 'journal')) + fmt = fss('getbool', 'do_format') + ui.command(':pformat.set', fmt ) + ui.command(':ovl_journal.enable', fmt) + ui.command(':persist.set', fss('getbool', 'do_persist')) + + ui.command(':larchpart.text') # clear the destination + docviewer.gohome('gui_medium.html') + + + def data(self, key): + return ui.command('medium_page_data.get', key) + + + def mediumtype(self, index): + if (index == None): + if self.medium == None: + self.medium = self.data('media')[0] + else: + self.medium = self.data('media')[index] + + if self.medium == 'medium-boot': + label = fss('get_bootisolabel') + ui.command(':srctype.setindex', self.data('sources').index('device')) + ui.command(':srctype.enable', False) + ifile = fss('getbootisofile') + + else: + label = fss('get_mediumlabel') + ui.command(':srctype.enable', True) + + if self.medium == 'medium-iso': + ifile = fss('getisofile') + + self.showlabel(label) + + if self.medium == 'medium-w': + ui.command(':mediumopts.enable', True) + self.setdestinationpath('') + + else: + ui.command(':mediumopts.enable', False) + idir = fss('getisosavedir') + if not fss('isdir', idir): + fss('setitem', 'isosavedir', '') + idir = fss('getisosavedir') + self.setdestinationpath(os.path.join(idir, ifile)) + + if index != None: + self.enableprofile() + self.enablemake() + + + def srctype(self, index): + if (index == None): + if self.srcmedium == None: + self.srcmedium = self.data('sources')[0] + else: + self.srcmedium = self.data('sources')[index] + + if self.srcmedium == 'larchified': + self.setsourcepath(self.source) + ui.command(':selectsrc.enable', False) + + elif self.srcmedium == 'device': + self.setsourcepath('') # clear source, it must be selected + ui.command(':selectsrc.enable', True) + + elif self.srcmedium == 'isofile': + isof = fss('getisofile') + isod = fss('getisosavedir') + self.pendingpath = os.path.join(isod, isof) + ui.command(':selectsrc.enable', True) + self.setsourcepath('') + self.checklarchsource() + + + def setsourcepath(self, path): + self.sourcepath = path + ui.command(':srclocation.text', path) + self.enableprofile() + self.enablemake() + + + def choosesrc(self): + # 'larchified' should not be possible - it is set on the project page + if self.srcmedium == 'device': + self.devices = [] + larchcall('*rootfn', self._cd_line, 'blkid -c dev/null -o list') + + elif self.srcmedium == 'isofile': + # Pop up a file browser + self.pendingpath = self.isopath(mode='source') + self.checklarchsource() + + else: + debug('page_medium: Medium.choosesrc / ' + self.srcmedium) + + def _cd_line(self, line): + if line == None: + # Completed - pop up device chooser + ok, choice = ui.command('listDialog', + self.data('parts_src'), + self.data('parts_t'), + self.devices, len(self.devices) - 1) + if ok: + self.pendingpath = choice.split()[0] + ui.idle_add(self.checklarchsource) + + else: + l = line.strip() + if l.startswith('/dev/'): + i = l.find('(not mounted)') + if i > 0: + ls = l[:i].split(None, 2) + if (ls[0] != self.destinationpath) and (len(ls) == 3): + self.devices.append('%-10s %s' % (ls[0], ls[2])) + + + def checklarchsource(self): + if self.pendingpath: + # check it is really a larch medium + self.larchok = False + larchcall('*larchmedium', self._cs_line, self.pendingpath) + + def _cs_line(self, line): + if line == None: + # Completed + if self.larchok: + self.setsourcepath(self.pendingpath) + else: + l = line.strip() + if l.startswith('##--'): + if l.endswith('ok'): + self.larchok = True + + + def enableprofile(self): + """Set the enabled state of the medium profile frame, + according to the state of the choices. + """ + ui.command(':mediumprofile.enable', (self.medium != 'medium-boot') + and (self.srcmedium == 'larchified')) + + + def enablemake(self): + on = bool(self.destinationpath) and bool(self.sourcepath) + if self.medium == 'medium-w': + formatting = fss('getbool', 'do_format') + self.enablepersist(formatting or (self.fsok in OKFS)) + if (not formatting): + if (self.fsok not in OKFS) and (self.fsok != 'vfat'): + on = False + ui.command(':make_medium.enable', on) + + + def enablepersist(self, on): + ui.command(':persist.enable', on) + self.persist_enabled = on + + + def persistence(self, on): + fss('setbool', 'do_persist', on) + + + def journal(self, on): + fss('setbool', 'journal', on) + + + def mediumsearch(self, option): + choice = self.detectionmodes[option] + fss('setitem', 'medium_search', choice) + self.nlbenable(choice) + + + def nlbenable(self, choice): + ui.command(':nolarchboot.enable', choice != 'search') + + + def bootnosearch(self, on): + fss('setbool', 'boot_nosearch', on) + + + def setpformat(self, on): + fss('setbool', 'do_format', on) + ui.command(':ovl_journal.enable', on) + self.enablemake() + + + def editbootlines(self): + f0 = 'profile:cd-root/boot0/bootlines' + if not fss('isfile', f0): + f0 = 'base:cd-root/boot0/bootlines' + edit('profile:cd-root/boot/bootlines', 'base:cd-root/boot0/bootlines') + + + def editsyslin(self): + f0 = 'profile:cd-root/boot0/isolinux/isolinux.cfg' + if not fss('isfile', f0): + f0 = 'base:cd-root/boot0/isolinux/isolinux.cfg' + edit('profile:cd-root/boot/isolinux/isolinux.cfg', f0) + + + def browsecdroot(self): + fss('browse', 'profile:cd-root') + + + def newlabel(self): + labelsrc = 'bootiso' if self.medium == 'medium-boot' else 'medium' + ok, l = ui.command('textLineDialog', + self.data('prompt_label'), + None, fss('get_%slabel' % labelsrc)) + if ok: + self.showlabel(fss('set_%slabel' % labelsrc, l)) + + + def showlabel(self, l): + ui.command(':vlabele.text', l) + + + def choosedest(self): + if self.medium == 'medium-w': + # Present a list of unmounted partitions + self.devices = [] + larchcall('*rootfn', self._sd_line, 'blkid -c /dev/null -o list') + + elif self.medium == 'medium-iso': + # Pop up a file browser + path = self.isopath(mode='source') + if path: + self.setdestinationpath(path) + + elif self.medium == 'medium-boot': + # Pop up a file browser + path = self.isopath(mode='bootiso') + if path: + self.setdestinationpath(path) + + def _sd_line(self, line): + if line == None: + nmdevices = [] + for part in fss('get_partitions'): # ->(dev, size in MiB(int)) + if part[0] == self.sourcepath: + continue + found = False + for partinfo in self.devices: + if partinfo[0] == part[0]: + if partinfo[2]: + nmdevices.append('%-12s %8d MiB %-10s %s' + % (part[0], part[1], partinfo[1], partinfo[2])) + found = True + break + if not found: + nmdevices.append('%-12s %-12d MiB' % (part[0], part[1])) + + # Completed - pop up device chooser + ok, choice = ui.command('listDialog', + self.data('parts_dst'), + self.data('parts_t'), + nmdevices, len(nmdevices) - 1) + if ok: + ui.idle_add(self.setdestinationpath, choice.split()[0]) + + else: + l = line.strip() + if l.startswith('/dev/'): + i = l.find('(not mounted)') + if i > 0: + # Try to get label for unmounted devices only + ls = l[:i].split(None, 2) + if len(ls) < 3: + ls.append('-') # signifies 'no label' + else: + ls = l.split(None, 2) + ls[2] = None # mark the partition as mounted + self.devices.append(ls) + + + def setdestinationpath(self, path): + ui.command(':larchpart.text', path) + self.destinationpath = path + if path.startswith('/dev/'): + # Check the file-system + self.fsok = None + larchcall('*rootfn', self._em_line, + 'blkid -c /dev/null -o value -s TYPE %s' % path) + else: + self.enablemake() + + def _em_line(self, line): + if line == None: + # Completed + self.enablemake() + else: + line = line.strip() + if line: + self.fsok = line + + + def isopath(self, mode='dest'): + sdir = fss('getisosavedir') + ifname = fss('getbootisofile' if mode=='bootiso' else 'getisofile') + path = ui.fileDialog(self.data('isoget' if mode=='source' else 'isopath'), + startdir=sdir, create=(mode!='source'), + 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 mode=='bootiso' else 'isofile', f) + return path + + return None + + + def make(self): + """Write the larch medium. + """ + if self.srcmedium == 'larchified': + source = None + else: + source = self.sourcepath + if not source: + debug("page_medium: make / null source") + return + + args = ['-l', ui.command(':vlabele.get')] + + if self.medium == 'medium-boot': + # Write a boot iso file + args += ['-b', '-o', self.destinationpath] + larchcall('writemedium', source, args) + + elif self.medium == 'medium-iso': + # Write an 'iso' file + args += ['-o', self.destinationpath] + larchcall('writemedium', source, args) + + else: + # Write to partition + # Medium detection options + detect = fss('getitem', 'medium_search') + args += ['-d', detect] + if (detect != 'search') and ui.command(':nolarchboot.active'): + args.append('-n') + # Formatting + if fss('getbool', 'do_format'): + # Journalling? + if not fss('getbool', 'journal'): + args.append('-j') + else: + args.append('-F') + # Set master boot record? + if ui.command(':nombr.active'): + args.append('-m') + # Persistence + if fss('getbool', 'do_persist') and self.persist_enabled: + args.append('-P') + # Add the medium to the argument list + args.append(self.destinationpath) + larchcall('writemedium', source, args) diff --git a/build_tools/larch8/larch0/gui/front/page_project.py b/build_tools/larch8/larch0/gui/front/page_project.py new file mode 100644 index 0000000..cbbda66 --- /dev/null +++ b/build_tools/larch8/larch0/gui/front/page_project.py @@ -0,0 +1,241 @@ +# 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.08.15 + +import os + +class ProjectSettings: + def __init__(self): + ui.widgetlist(fss('fetch_layout', 'page_project.uim')) + ui.widgetlist(fss('fetch_layout', 'profile_browse.uim')) + ui.command('dialog:profile_browser.pack') + + 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.copy_profile), + (':profile_clone*clicked', self.clone_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), + ('dpb:example_list*changed', self.dpb_example), + ('dpb:browse*clicked', self.dpb_browse), + ('dpb:name_s*clicked', self.dpb_source), + ) + + + 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): + self.example_profile_dir, self.dpb_example_list = fss('get_example_profiles') + ui.command('dpb:example_list.set', self.dpb_example_list, -1) + if ui.command('dialog:profile_browser.showmodal'): + source = ui.command('dpb:source.get') + name = ui.command('dpb:name.get') + if name in self.profiles: + if not ui.command('confirmDialog', self.data('prompt_pr')): + return + if fss('get_new_profile', source, name): + self.setup() + else: + run_error(self.data('msg_npd') % source) + + + def dpb_example(self, index): + name = self.dpb_example_list[index] + ui.command('dpb:source.text', self.example_profile_dir + '/' + name) + ui.command('dpb:name.text', name) + + + def dpb_browse(self): + source = ui.fileDialog(self.data('file_ps'), dirsonly=True, + startdir=fss('getitem', 'profile_browse_dir')) + if source: + fss('setitem', 'profile_browse_dir', os.path.dirname(source)) + ui.command('dpb:source.text', source) + ui.command('dpb:name.text', os.path.basename(source)) + ui.command('dpb:example_list.set', self.dpb_example_list, -1) + + + def dpb_source(self): + ok, name = ui.command('textLineDialog', _("Name for new profile:"), None, + os.path.basename(ui.command('dpb:source.get'))) + if ok: + ui.command('dpb:name.text', name) + + + 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 clone_profile(self): + ok, name = ui.command('textLineDialog', self.data('prompt_clone'), self.profile_name) + if ok: + name = name.strip() + self.save_profile(name) + fss('set_profile', name) + self.setup() + + def copy_profile(self): + startdir = fss('getitem', 'profile_browse_dir') + path = ui.fileDialog(self.data('file_sp'), + create=True, dirsonly=True, file=self.profile_name, + startdir=startdir) + if path: + fss('set_profile_browse_dir', path) + self.save_profile(path) + + def save_profile(self, 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')) + + |