#!/usr/bin/env python3 #-*- coding: utf-8 -*- # kirmah/ui.py # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # software : Kirmah # version : 2.18 # date : 2014 # licence : GPLv3.0 # author : a-Sansara <[a-sansara]at[clochardprod]dot[net]> # copyright : pluie.org # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # This file is part of Kirmah. # # Kirmah is free software (free as in speech) : you can redistribute it # and/or modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the License, # or (at your option) any later version. # # Kirmah 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 Kirmah. If not, see . # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~ module ui ~~ from gi.repository import Pango from gi.repository.Gdk import threads_enter, threads_leave from gi.repository.Gtk import AboutDialog, Builder, main as main_enter, main_quit, MessageDialog, MessageType, ButtonsType, ResponseType, PackType from gi.repository.GdkPixbuf import Pixbuf from gi.repository.GObject import threads_init, GObject, idle_add, SIGNAL_RUN_LAST, TYPE_NONE, TYPE_STRING, TYPE_FLOAT, TYPE_BOOLEAN from threading import Thread, current_thread, enumerate as thread_enum from multiprocessing import Event from psr.sys import Sys, Io, Const from psr.log import Log from kirmah import conf from kirmah.cli import Cli # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~ class Gui ~~ class Gui(): @Log(Const.LOG_BUILD) def __init__(self, wname): """""" threads_init() self.wname = wname self.builder = Builder() self.builder.add_from_file(conf.PRG_GLADE_PATH) self.builder.connect_signals(self) self.win = self.get(wname) self.win.connect('destroy', self.onDeleteWindow) self.win.connect('delete-event', self.onDeleteWindow) self.win.set_title(conf.PRG_NAME+' v'+conf.PRG_VERS) self.win.show_all() self.on_start() main_enter() @Log(Const.LOG_DEBUG) def buildTxtTags(self, textbuffer): tags = {} tags[Const.CLZ_TIME] = textbuffer.create_tag(Const.CLZ_TIME , foreground="#208420", weight=Pango.Weight.BOLD) tags[Const.CLZ_SEC] = textbuffer.create_tag(Const.CLZ_SEC , foreground="#61B661", weight=Pango.Weight.BOLD) tags[Const.CLZ_DEFAULT] = textbuffer.create_tag(Const.CLZ_DEFAULT , foreground="#FFEDD0") tags[Const.CLZ_IO] = textbuffer.create_tag(Const.CLZ_IO , foreground="#EB3A3A", weight=Pango.Weight.BOLD) tags[Const.CLZ_FUNC] = textbuffer.create_tag(Const.CLZ_FUNC , foreground="#EBEB3A", weight=Pango.Weight.BOLD) tags[Const.CLZ_CFUNC] = textbuffer.create_tag(Const.CLZ_CFUNC , foreground="#EBB33A", weight=Pango.Weight.BOLD) tags[Const.CLZ_DELTA] = textbuffer.create_tag(Const.CLZ_DELTA , foreground="#397BE8", weight=Pango.Weight.BOLD) tags[Const.CLZ_ARGS] = textbuffer.create_tag(Const.CLZ_ARGS , foreground="#A1A1A1") tags[Const.CLZ_ERROR] = textbuffer.create_tag(Const.CLZ_ERROR , background="#830005", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_ERROR_PARAM] = textbuffer.create_tag(Const.CLZ_ERROR_PARAM , background="#830005", foreground="#EBEB3A", weight=Pango.Weight.BOLD) tags[Const.CLZ_WARN] = textbuffer.create_tag(Const.CLZ_WARN , background="#A81459", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_WARN_PARAM] = textbuffer.create_tag(Const.CLZ_WARN_PARAM , background="#A81459", foreground="#EBEB3A", weight=Pango.Weight.BOLD) tags[Const.CLZ_PID] = textbuffer.create_tag(Const.CLZ_PID , background="#5B0997", foreground="#E4C0FF", weight=Pango.Weight.BOLD) tags[Const.CLZ_CPID] = textbuffer.create_tag(Const.CLZ_CPID , background="#770997", foreground="#F4CDFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_SYMBOL] = textbuffer.create_tag(Const.CLZ_SYMBOL , background="#61B661", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_OK] = textbuffer.create_tag(Const.CLZ_OK , background="#167B3B", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_KO] = textbuffer.create_tag(Const.CLZ_KO , background="#7B1716", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_TITLE] = textbuffer.create_tag(Const.CLZ_TITLE , foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_TASK] = textbuffer.create_tag(Const.CLZ_TASK , foreground="#61B661", weight=Pango.Weight.BOLD) tags[Const.CLZ_HEAD_APP] = textbuffer.create_tag(Const.CLZ_HEAD_APP , background="#2B5BAB", foreground="#FFFFFF", weight=Pango.Weight.BOLD) tags[Const.CLZ_HEAD_SEP] = textbuffer.create_tag(Const.CLZ_HEAD_SEP , foreground="#A1A1A1") tags[Const.CLZ_HEAD_KEY] = textbuffer.create_tag(Const.CLZ_HEAD_KEY , foreground="#EBEB3A", weight=Pango.Weight.BOLD) tags[Const.CLZ_HEAD_VAL] = textbuffer.create_tag(Const.CLZ_HEAD_VAL , foreground="#397BE8", weight=Pango.Weight.BOLD) return tags @Log(Const.LOG_UI) def onDeleteWindow(self, *args): """""" mthread = current_thread() try: self.join_threads(True) self.cleanResources() except Exception as e: pass finally: main_quit(*args) @Log() def beforeDelete(self): """""" @Log(Const.LOG_UI) def list_threads(self): """""" print('thread list : ') for th in thread_enum(): print(th) @Log(Const.LOG_UI) def join_threads(self, join_main=False): """""" mthread = current_thread() try: for th in thread_enum(): if th is not mthread : th.join() if join_main: mthread.join() except Exception as e: pass @Log(Const.LOG_UI) def on_about(self, btn): """""" about = AboutDialog() about.set_program_name(conf.PRG_NAME) about.set_version('v '+conf.PRG_VERS) about.set_copyright(conf.PRG_ABOUT_COPYRIGHT) about.set_comments(conf.PRG_ABOUT_COMMENTS) about.set_website(conf.PRG_WEBSITE) about.set_website_label(conf.PRG_WEBSITE) about.set_license(Io.get_data(conf.PRG_LICENSE_PATH)) pixbuf = Pixbuf.new_from_file_at_size(conf.PRG_LOGO_PATH, conf.PRG_ABOUT_LOGO_SIZE, conf.PRG_ABOUT_LOGO_SIZE) about.set_logo(pixbuf) pixbuf = Pixbuf.new_from_file_at_size(conf.PRG_LOGO_PATH, conf.PRG_ABOUT_LOGO_SIZE, conf.PRG_ABOUT_LOGO_SIZE) about.set_icon(pixbuf) about.run() about.destroy() @Log(Const.LOG_DEBUG) def get(self, name): """""" return self.builder.get_object(name) @Log(Const.LOG_DEBUG) def disable(self, name, disabled): """""" self.get(name).set_sensitive(not disabled) @Log(Const.LOG_DEBUG) def repack(self, name, expandfill=False, packStart=True): w = self.get(name) w.get_parent().set_child_packing(w, expandfill, expandfill, 0, PackType.START if packStart else PackType.END ) return w @Log(Const.LOG_DEBUG) def detachWidget(self, name, hideParent=True): w = self.get(name) wp = w.get_parent() if wp is not None : wp.remove(w) w.unparent() if hideParent : wp.hide() @Log(Const.LOG_DEBUG) def attachWidget(self, widget, parentName, expandfill=None, showParent=True): if widget is not None : wp = self.get(parentName) if wp is not None : if expandfill is None : wp.add(widget) else : wp.pack_start(widget,expandfill,expandfill,0) if showParent : wp.show() @Log(Const.LOG_UI) def thread_finished(self, thread, ref): thread = None self.on_proceed_end(False) @Log(Const.LOG_UI) def on_proceed_end(self, abort=False): """""" @Log(Const.LOG_UI) def thread_interrupted(self, thread, ref): thread = None self.end_progress() self.on_proceed_end(False) @Log(Const.LOG_NEVER) def thread_progress(self, thread, progress, ref): while not Sys.g.LOG_QUEUE.empty(): data = Sys.g.LOG_QUEUE.get() if data is not None : if data is not Sys.g.SIGNAL_STOP : cth, data = data for item in data : ei = self.textbuffer.get_end_iter() offs = ei.get_offset() self.textbuffer.insert_at_cursor(item[0]) ei = self.textbuffer.get_end_iter() oi = self.textbuffer.get_iter_at_offset(offs) tagName = item[1] self.textbuffer.apply_tag(self.tags[tagName], oi, ei) self.textbuffer.insert_at_cursor('\n') self.scroll_end() else : Sys.dprint('STOP') thread.cancel() self.update_progress(progress) @Log(Const.LOG_NEVER) def update_progress(self, progress, lvl=50): if progress > 0 : self.progressbar.set_text(str(progress)) lp = self.progressbar.get_fraction() diff = (progress/100.0 - lp) for i in range(lvl): nf = lp+(i*diff/lvl) if nf < progress/100.0 : self.progressbar.set_fraction(nf) self.progressbar.set_fraction(progress/100.0) else : self.progressbar.set_fraction(self.progressbar.get_fraction()+0.01) @Log(Const.LOG_NEVER) def end_progress(self): self.update_progress(100, 10) @Log(Const.LOG_NEVER) def scroll_end(self): if Sys.g.UI_AUTO_SCROLL : if self.textbuffer is not None : insert_mark = self.textbuffer.get_insert() ei = self.textbuffer.get_end_iter() if ei is not None and insert_mark is not None: self.textbuffer.place_cursor(ei) self.textview.scroll_to_mark(insert_mark , 0.0, True, 0.0, 1.0) @Log(Const.LOG_UI) def cleanResources(self): """""" @Log(Const.LOG_UI) def on_start(self): """""" @Log(Const.LOG_UI) def warnDialog(self, intro, ask): """""" dialog = MessageDialog(self.get(self.wname), 0, MessageType.WARNING, ButtonsType.OK_CANCEL, intro) dialog.format_secondary_text(ask) response = dialog.run() dialog.destroy() return response == ResponseType.OK; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~ class IdleObject ~~ class IdleObject(GObject): """ Override gi.repository.GObject to always emit signals in the main thread by emmitting on an idle handler """ @Log(Const.LOG_UI) def __init__(self): """""" GObject.__init__(self) @Log(Const.LOG_NEVER) def emit(self, *args): """""" idle_add(GObject.emit, self, *args) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~ class CliThread ~~ class CliThread(Thread, IdleObject): """ Cancellable thread which uses gobject signals to return information to the GUI. """ __gsignals__ = { # signal type signal return signal args "completed" : ( SIGNAL_RUN_LAST, TYPE_NONE, ()), "interrupted" : ( SIGNAL_RUN_LAST, TYPE_NONE, ()), "progress" : ( SIGNAL_RUN_LAST, TYPE_NONE, (TYPE_FLOAT,)) } @Log(Const.LOG_DEBUG) def __init__(self, rwargs, event): Thread.__init__(self) IdleObject.__init__(self) self.setName('CliThread') self.cliargs = rwargs self.event = event @Log(Const.LOG_DEBUG) def run(self): """""" self.cancelled = False Sys.g.MPEVENT.clear() print(Sys.g.LOG_LEVEL) Cli('./', Sys.getpid(), self.cliargs, self, Sys.g.LOG_LEVEL) self.emit("completed") @Log(Const.LOG_NEVER) def progress(self, value): """""" self.emit("progress", value) @Log(Const.LOG_NEVER) def cancel(self): """ Threads in python are not cancellable, so we implement our own cancellation logic """ self.cancelled = True self.event.set() @Log(Const.LOG_NEVER) def stop(self): """""" if self.isAlive(): self.cancel() if current_thread().getName()==self.getName(): try: self.emit("interrupted") Sys.thread_exit() except RuntimeError as e : print(str(self.getName()) + ' COULD NOT BE TERMINATED') raise e