#!/usr/bin/env python3 ################################################### # # # User interface for syncer-plank # # Author: M.Angel Juan # # License: GPLv3 # # # ################################################### ############################## # # Logging & debug helper functions # ############################## #import logging #from colorlog import ColoredFormatter #from functools import wraps import time # #def setup_logger(): # """Return a logger with a default ColoredFormatter.""" # formatter = ColoredFormatter( # "(%(threadName)-9s) %(log_color)s%(levelname)-8s%(reset)s %(message_log_color)s%(message)s", # datefmt=None, # reset=True, # log_colors={ # 'DEBUG': 'cyan', # 'INFO': 'green', # 'WARNING': 'yellow', # 'ERROR': 'red', # 'CRITICAL': 'red', # }, # secondary_log_colors={ # 'message': { # 'ERROR': 'red', # 'CRITICAL': 'red', # 'DEBUG': 'yellow' # } # }, # style='%' # ) # # logger = logging.getLogger(__name__) # handler = logging.StreamHandler() # handler.setFormatter(formatter) # logger.addHandler(handler) # logger.setLevel(logging.DEBUG) # # return logger # #def trace(func): # """Tracing wrapper to log when function enter/exit happens. # :param func: Function to wrap # :type func: callable # """ # @wraps(func) # def wrapper(*args, **kwargs): # t=time.time() # strdebug='{0:0.3f} Start {1!r} '. format(t-start_time,func.__name__) # #strdebug+='KWARGS={} ARGS={}'.format(kwargs,args) # # # # Filter parameters # # # out={} # for x in kwargs: # text=str(kwargs[x]) # if len(text) < 30 and 'object at' not in text: # out[x]=str(kwargs[x]) # else: # out[x]='Skipped' # i=0 # for x in args: # text=str(x) # if len(text) < 30 and 'object at' not in text: # out['arg' + str(i)] = x # else: # out['arg' + str(i)] = 'Skipped' # i += 1 # strdebug += 'Parameters: {}'.format(out) # logger.debug(strdebug) # result = func(*args, **kwargs) # logger.debug('+{0:0.3f} End {1!r}'. format(time.time()-t,func.__name__)) # return result # return wrapper # #start_time=time.time() #logger = setup_logger() # def dbg(*args,**kwargs): enable=False if enable: for x in args: print(str(x)) def check_args(*args,**kwargs): for a in args: if a not in kwargs: str='{} not in kwargs!'.format(a) raise Exception(str) return True import ctypes def terminate_thread(thread): """Terminates a python thread from another thread. :param thread: a threading.Thread instance """ if not thread.isAlive(): return exc = ctypes.py_object(SystemExit) res = ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), exc) if res == 0: raise ValueError("nonexistent thread id") elif res > 1: # """if it returns a number greater than one, you're in trouble, # and you should call it again with exc=NULL to revert the effect""" ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None) raise SystemError("PyThreadState_SetAsyncExc failed") ############################## # # Imports section # ############################## import sys,os import gi import re import threading import time import glob import xmlrpc.client as x import ssl import subprocess import tempfile import base64 import shutil gi.require_version('Gtk','3.0') from gi.repository import Gtk,GdkPixbuf,GObject,Gdk,GLib,Gio # # i18n # import gettext from gettext import gettext as _ gettext.bindtextdomain('syncer_plank_gui','/usr/share/locale') gettext.textdomain('syncer_plank_gui') ############################## # # Helper functions, threading & n4d mainly # ############################## class Helper: # # Helper initialization # def __init__(self,*args,**kwargs): check_args('controller',**kwargs) self.ctl=kwargs['controller'] # # Thread pool initialization # self._thread = [None] * 10 # self.get_used_thread = lambda : self._thread.index(list(filter(None.__ne__,self._thread))[0]) self.get_thread = lambda: self._thread.index(None) # # N4d initialization # self.n4d = x.ServerProxy("https://server:9779", verbose=False, use_datetime=True, context=ssl._create_unverified_context()) self.n4d_sepaphore = threading.BoundedSemaphore(value=1) # # Method to create threaded job, with or without callback # #@trace def do_call(self, func, args, callback, nthread=None, prio=GLib.PRIORITY_HIGH_IDLE,block=False): # Local thread number thread = None # If we are checking finalization of already created thread if nthread == None: # Is a new call try: try: # Get a free thread pool space thread = self.get_thread() namethread='thread_num_{}'.format(thread) dbg('Launch {} with {}'.format(namethread,func.__name__)) except: raise Exception('No more threads available') # Create thread on pool if args!= None: # Thread needs args self._thread[thread] = threading.Thread(target=func, args=(args,),name=namethread) else: # Thread without args self._thread[thread] = threading.Thread(target=func,name=namethread) # Need a threaded blocking call? if block: # Start blocking thread self._thread[thread].start() self._thread[thread].join() # Free thread self._thread[thread] = None # If has callback, call passing result or return the result of thread directly if callback == None: return args['result'] else: return callback(**args) and False else: # Start normal thread out of gtk loop (non-blocking) GObject.idle_add(self._thread[thread].start, priority=prio) # Program next check thread finalization GObject.timeout_add(50, self.do_call, func, args, callback, thread, prio,block) # Finalize main call, no need to re-initialize timeouted checking return False except: # Something goes wrong :-( raise Exception('Error calling threads') else: # No new call, check if its finalized # we use the old thread in pool, already started thread = nthread # Check if the job is done if self._thread[thread].isAlive() or not self._thread[thread]._started._flag: # If not done, initialize timeout checking return True else: # Job is done # Free the thread pool space self._thread[thread] = None try: # If has callback, call passing result or return the result of thread directly if callback != None: dbg('Callback from thread_num_{}: running function \'{}\''.format(nthread,callback.__name__)) return callback(**args) and False else: # No direct return value is possible due to non blocking call, result only can be catched by asynchronous function # Finalize main call, no need to re-initialize timeouted checking return False except Exception as e: # Something goes wrong :-( raise e # In normal conditions no need to reach this point # Finalize main call, no need to re-initialize timeouted checking return False ############################## # # Search plank elements into user home # ############################## def search_local_plank(self,*args,**kwargs): check_args('files_cache',**kwargs) files_cache=kwargs['files_cache'] list_apps=[] theme='Vibrancy-Colors' try: file_names = [filename for filename in glob.iglob(os.path.expanduser('~') + '/.config/plank/dock1/launchers/*.dockitem', recursive=True)] except Exception as e: return list_apps for filename in file_names: try: basename=os.path.splitext(os.path.basename(filename))[0] if basename == 'desktop': icon_file='preferences-desktop-wallpaper.png' complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file, recursive=True)][0] name='Show desktop' if complete_filename: list_apps.append((complete_filename,basename,basename,filename,name)) elif basename == 'matecc': icon_file='preferences-desktop.png' complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/'+icon_file, recursive=True)][0] name='Control center' if complete_filename: list_apps.append((complete_filename,basename,basename,filename,name)) else: complete_filename=[filename for filename in glob.iglob('/usr/share/applications/'+basename+'.desktop', recursive=True)][0] result=self.scan_desktop_file(filename=complete_filename,files_cache=files_cache) if result: list_apps.append(result) except Exception as e: dbg('{} processing local filename \'{}\''.format(str(e),filename)) pass return list_apps ############################## # # Search desktop elements into user desktop # ############################## def get_desktop_folder(self,*args,**kwargs): name = None try: name = subprocess.check_output(['xdg-user-dir','DESKTOP']).decode().strip() except Exception as e: dbg('{} getting desktop folder from xdg'.format(str(e), filename)) return name ############################## # # Search desktop elements into user desktop # ############################## def search_local_desktops(self, *args, **kwargs): check_args('files_cache', **kwargs) files_cache = kwargs['files_cache'] list_apps = [] theme = 'Vibrancy-Colors' desktop_folder = self.get_desktop_folder() try: file_names = [filename for filename in glob.iglob(desktop_folder+'/*.desktop', recursive=True)] except Exception as e: return list_apps for filename in file_names: try: basename = os.path.splitext(os.path.basename(filename))[0] if basename == 'desktop': icon_file = 'preferences-desktop-wallpaper.png' complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file, recursive=True)][0] name = 'Show desktop' if complete_filename: list_apps.append((complete_filename, basename, basename, filename, name)) elif basename == 'matecc': icon_file = 'preferences-desktop.png' complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file, recursive=True)][0] name = 'Control center' if complete_filename: list_apps.append((complete_filename, basename, basename, filename, name)) else: result = self.scan_desktop_file(filename=filename, files_cache=files_cache) if result: list_apps.append(result) except Exception as e: dbg('{} processing local filename \'{}\''.format(str(e), filename)) return list_apps ############################## # # Search location of icon appname # ############################## def search_icon_file(self,*args,**kwargs): check_args('files_cache','icon',**kwargs) files_cache=kwargs['files_cache'] nameicon=kwargs['icon'] try: file_names='' txt='(.*'+nameicon+'.png)' regexp=re.compile(txt) for filename in files_cache: m=regexp.match(filename) if m: file_names=m.group(1) break except Exception as e: raise(e) if len(file_names) == 0: raise Exception('Icon file {} not found'.format(nameicon)) return file_names ############################## # # Parse & extract info from desktop file # ############################## def scan_desktop_file(self,*args,**kwargs): check_args('files_cache','filename',**kwargs) filename=kwargs['filename'] files_cache=kwargs['files_cache'] regexp1 = re.compile('Icon=(.*)',re.IGNORECASE) regexp2 = re.compile('Name=(.*)', re.IGNORECASE) regexp3 = re.compile('Comment=(.*)',re.IGNORECASE) try: with open(filename, 'r') as f: lines = f.readlines() icon = None appname = None description = None for line in lines: m1 = regexp1.match(line) m2 = regexp2.match(line) m3 = regexp3.match(line) if m1 and icon == None: icon = m1.group(1) if m2 and appname == None: appname = m2.group(1) if m3 and m3.group(1): description=m3.group(1) # # Commented due to allow desktops with no icon or description # #if icon and appname and description: if appname: desktop_basename = os.path.splitext(os.path.basename(filename))[0] if icon != None: if 'png' in icon.split('.'): icon_path = icon else: try: icon_path = self.search_icon_file(icon=icon,files_cache=files_cache) except Exception as e: # Allow tolerance if no icon is found icon_path = None if desktop_basename: return (icon_path, appname, desktop_basename,filename,description) else: # icon or description are None return (icon,appname,desktop_basename,filename,description) else: # raise Exception('Can\'t get icon/appname/description from desktop file \'{}\''.format(filename)) raise Exception('Can\'t get appname from desktop file \'{}\''.format(filename)) except Exception as e: raise(e) ############################## # # Search system applications # ############################## def search_in_applications(self,*args,**kwargs): check_args('cache_info','files_cache',**kwargs) info=kwargs['cache_info'] files_cache=kwargs['files_cache'] try: file_names = [filename for filename in glob.iglob('/usr/share/applications/*.desktop', recursive=True)] except: return [] # LIMIT #file_names = file_names[1:50] outlist=[] procesed_items=0 total_items=len(file_names) info['msg'] = _("Scanning system apps...") # FILTER SOME ELEMENTS WITH SAME NAME not_repeated_list = {} filter_column = 1 column4change_if_seems_the_same = 2 for desktop in file_names: try: result=self.scan_desktop_file(filename=desktop,files_cache=files_cache) if result: if result[filter_column] not in not_repeated_list: not_repeated_list[result[filter_column]]=result else: # if seems the same element change 'name' with 'basename' # First change old element found temp1=list(not_repeated_list[result[filter_column]]) del not_repeated_list[result[filter_column]] temp1[filter_column]=temp1[column4change_if_seems_the_same] not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp1) # Second insert new element found temp2=list(result) temp2[filter_column]=temp2[column4change_if_seems_the_same] not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp2) except Exception as e: dbg(e) pass procesed_items += 1 info['level']=procesed_items/total_items for x in not_repeated_list: outlist.append(not_repeated_list[x]) return outlist ############################## # # Clean status_bar_with_timeout # ############################## def clean_status_bar(self,*args,**kwargs): def sleep_and_clean(x): time.sleep(x.get('sleep')) bar_info = {str(x.get('num_bar')): ''} self.ctl.window.set_txt_status_bars(bar_info=bar_info) self.do_call(sleep_and_clean,{'sleep':3,'num_bar':args[0]},None) return ############################## # # N4D validation call # ############################## #@trace def n4d_validate(self,*args,**kwargs): check_args('user','pwd',**kwargs) d = {'args': (kwargs['user'], kwargs['pwd']),'action':'validation_end'} #login_func = lambda x: x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))}) #@trace def login_func(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) return self.do_call(login_func, d, self.ctl.process) ############################## # # N4D call to get ldap groups # ############################## #@trace def n4d_get_system_groups(self,*args,**kwargs): check_args('user','pwd',**kwargs) d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_groups'} #get_groups = lambda x: x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')}) #@trace def get_groups(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) return self.do_call(get_groups, d, self.ctl.process) ############################## # # N4D call to get currently stored profiles # ############################## #@trace def n4d_get_profiles_stored(self,*args,**kwargs): check_args('user','pwd',**kwargs) d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_profiles'} #get_profiles = lambda x: x.update({'result': self.n4d.get_profiles(x.get('args'),'PlankSync')}) #@trace def get_profiles(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.get_profiles(x.get('args'), 'PlankSync')}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) return self.do_call(get_profiles, d, self.ctl.process) ############################## # # N4D call to remove profile on filesystem # ############################## #@trace def n4d_remove_profile(self,*args,**kwargs): check_args('user','pwd','name',**kwargs) d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'],'action':'remove_profile_done'} #remove_profile = lambda x: x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))}) #@trace def remove_profile(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) return self.do_call(remove_profile, d, self.ctl.process) ############################## # # N4D call to get details about profile # ############################## #@trace def n4d_read_profile(self,*args,**kwargs): # # Option to set new parameters # d = {} try: check_args('extraparam',**kwargs) for nameparam in kwargs['extraparam']: d[nameparam]=kwargs['extraparam'][nameparam] except: pass check_args('user', 'pwd', 'name', **kwargs) d['args'] = (kwargs['user'],kwargs['pwd']) d['name'] = kwargs['name'] d['action'] = 'load_profile_into_liststore' #get_profile = lambda x: x.update({'result': self.window.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))}) #@trace def get_profile(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) # # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter # try: check_args('noprocess',**kwargs) new_callback_func=kwargs['noprocess'] callback=new_callback_func except: callback=self.ctl.process return self.do_call(get_profile, d, callback) ############################## # # N4D call to rename profile # ############################## # def n4d_rename_profile(self,*args,**kwargs): # check_args('user','pwd','old','new',**kwargs) # d = {'args': (kwargs['user'],kwargs['pwd']), 'oldname': kwargs['old'], 'newname': kwargs['new'],'action':'profile_is_renamed'} # #rename_profile = lambda x: x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))}) # def rename_profile(x): # try: # self.n4d_sepaphore.acquire() # x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))}) # self.n4d_sepaphore.release() # except Exception as e: # raise Exception(e) # return self.do_call(rename_profile, d, self.ctl.process) ############################## # # N4D call to create or update profile # ############################## def n4d_update_profile(self,*args,**kwargs): check_args('user','pwd','name','newname','grouplist','applist',**kwargs) d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'newname': kwargs['newname'], 'group': kwargs['grouplist'], 'applist': kwargs['applist'], 'action':'profile_is_updated'} try: check_args('status_bar',**kwargs) d.update({'status_bar':kwargs['status_bar']}) except: pass #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))}) def update_profile(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('newname'), x.get('group'), x.get('applist'))}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) # # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter # try: check_args('noprocess', **kwargs) new_callback_func = kwargs['noprocess'] callback = new_callback_func except: callback = self.ctl.process return self.do_call(update_profile, d, callback) def n4d_update_profile_groups(self,*args,**kwargs): check_args('user','pwd','name','grouplist',**kwargs) d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'group': kwargs['grouplist'], 'action':'profile_is_updated'} try: check_args('status_bar',**kwargs) d.update({'status_bar':kwargs['status_bar']}) except: pass #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))}) def update_profile_groups(x): try: self.n4d_sepaphore.acquire() x.update({'result': self.n4d.update_profile_groups(x.get('args'), 'PlankSync', x.get('name'), x.get('group'))}) self.n4d_sepaphore.release() except Exception as e: raise Exception(e) # # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter # try: check_args('noprocess', **kwargs) new_callback_func = kwargs['noprocess'] callback = new_callback_func except: callback = self.ctl.process return self.do_call(update_profile_groups, d, callback) def prepare_workspace(self,*args,**kwargs): check_args('profile',**kwargs) profile = kwargs['profile'] workspace = [] try: tempdir = tempfile.mkdtemp() for list_item in profile: type_item = list_item[0] content_item = list_item[1] if type_item == 'users': workspace.append(['users',content_item]) elif type_item == 'file': if content_item[0] == 'encoded': fp = tempfile.NamedTemporaryFile(mode='wb',delete=False, dir=tempdir) the_file = fp.name fp.write(base64.b64decode(content_item[2])) if not os.path.exists(tempdir+'/'+os.path.basename(content_item[1])): shutil.move(the_file,tempdir+'/'+os.path.basename(content_item[1])) the_file = tempdir+'/'+os.path.basename(content_item[1]) workspace.append(['file',the_file]) elif type_item == 'comm': workspace.append('comm') elif type_item == 'dconf': workspace.append('dconf') elif type_item in ['desktop','plank']: if content_item[0] == 'addcontent': workspace.append(type_item) if content_item[0] == 'add': workspace.append(['oldplank',content_item[1]]) to_remove=[] for i in range(len(workspace)-1,-1,-1): try: if workspace[i-1] == 'comm' and workspace[i-2][0] == 'file': workspace[i]=[workspace[i],workspace[i-2][1]] to_remove.append(i-1) to_remove.append(i-2) except: pass for i in sorted(to_remove,reverse=True): del workspace[i] to_remove = [] def search_in_cache(item): done = False out = [] for cl in self.ctl.model.cache_lists: for clt in self.ctl.model.cache_lists[cl]: if clt[2] == item: out = ['plank'] out.extend([clt[3]]) out.extend(list(clt)) done = True if done: break if done: break if done: return (True,out) else: return (False,None) for i in range(len(workspace)): if workspace[i][0] in ['desktop','plank']: with open(workspace[i][1],'r') as fp: filecontent = fp.read() if 'PlankDockItemPreferences' in filecontent: done,out = search_in_cache(os.path.basename(workspace[i][1])) if done: workspace[i]=out else: to_remove.append(i) else: try: content = self.scan_desktop_file(filename=workspace[i][1], files_cache=self.ctl.files_cache) workspace[i].extend(list(content)) except: to_remove.append(i) if workspace[i][0] == 'oldplank': # done=False # for cl in self.ctl.model.cache_lists: # for clt in self.ctl.model.cache_lists[cl]: # if clt[2] == workspace[i][1]: # workspace[i]= ['plank'] # workspace[i].extend([clt[3]]) # workspace[i].extend(list(clt)) # done=True # if done: # break # if done: # break done,out = search_in_cache(workspace[i][1]) if done: workspace[i]=out else: to_remove.append(i) for i in sorted(to_remove,reverse=True): del workspace[i] return workspace except Exception as e: return None ############################## # # Main visualization class # ############################## class MainWindow: def __init__(self,*args,**kwargs): check_args('controller','handler',**kwargs) self.ctl = kwargs['controller'] self.handler = kwargs['handler'] # # Show main window # self.obj = self.ctl.getobj('window_main') # # build stack components for the window # self.boxes_main = ['box_login','box_loading','box_main_btn','box_profiles2','box_dialog','box_pselector','box_group_edit','box_warning','box_edit_elements'] self.boxes_bottom = ['box_lvl','box_status'] self.stack_main = {'obj': self.build_stack(boxes=self.boxes_main),'stack':[]} self.stack_bottom = { 'obj': self.build_stack(boxes=self.boxes_bottom), 'stack':[]} self.dialog_filechooser = self.ctl.getobj('filechooser') # # get place to put stack # self.box_main_center=self.ctl.getobj('box_main_center') self.box_main_bottom=self.ctl.getobj('box_main_bottom') # # Add stacks # self.box_main_center.pack_start(self.stack_main['obj'],True,True,0) self.box_main_bottom.pack_start(self.stack_bottom['obj'],True,True,0) # # Store references to programatically created objects # boxes with labels and combos storing relations # self.group_profile_boxes = {} self.profiles_need_refresh = False # Show window self.obj.show_all() ############################## # # Builds dynamically boxes with an entry and comboboxes because # treeview with comboboxrenderer isn't a valid option # ############################## def build_group_profile_window(self,*args,**kwargs): # Get params from controller check_args('profiles','groups',**kwargs) profiles=kwargs['profiles'] groups=kwargs['groups'] # Get glade main object from builder container=self.ctl.getobj('box_container_grp_profiles') if self.profiles_need_refresh or len(self.group_profile_boxes) == 0: # # Profiles changed, need complete refresh # if self.profiles_need_refresh: self.profiles_need_refresh = False for box in container: box.destroy() self.group_profile_boxes = {} # # if it's the first time called build the structure # if it isn't the first time the objects are already stored # # Build ListStore 4 showing into ComboBox lst = Gtk.ListStore(GObject.TYPE_INT,GObject.TYPE_STRING) # Add fake empty first element lst.append([0,'[None]']) # Id entry into combobox model i=1 # Reverse mapping group -> profile association from list elements ['profile','group_associated'] grp_profile={} # Complete ListStore & do the mapping for profile in profiles: pname=profile[0] pgrp=profile[1] lst.append([i,pname]) if pgrp != '': for g in pgrp.split(','): grp_profile[g] = i i += 1 # Create Boxes (rows) per group for gname in groups: # A Box box=Gtk.Box(Gtk.Orientation.HORIZONTAL,10) box.set_property('homogeneous',True) box.set_property('halign',Gtk.Align.FILL) # With Label lbl=Gtk.Label(gname) lbl.set_property('halign',Gtk.Align.CENTER) # And ComboBox combo=Gtk.ComboBox.new_with_model_and_entry(lst) combo.set_property('halign',Gtk.Align.START) combo.set_property('width-request',200) combo.set_property('entry-text-column',1) combo.set_property('id-column', 0) # Set active element if already associated if gname in grp_profile: combo.set_property('active', grp_profile[gname]) else: combo.set_property('active', 0) # Connect signals combo.connect("changed", self.handler.combo_changed,lbl) # Pack everything box.pack_start(lbl,True,True,0) box.pack_start(combo,True,True,0) # Save references to avoid redo the objects self.group_profile_boxes.update({gname:[lbl,combo]}) container.pack_start(box,False,False,0) else: # # Objects are already created, they only need to be updated # # Id entry into combobox model i = 1 # Reverse mapping group -> profile association from list elements ['profile','group_associated'] grp_profile = {} # Do the mapping for profile in profiles: pgrp=profile[1] if pgrp != '': for g in pgrp.split(','): grp_profile[g] = i i += 1 for group in groups: if group in grp_profile: self.group_profile_boxes[group][1].set_property('active',grp_profile[group]) else: self.group_profile_boxes[group][1].set_property('active', 0) # Show all widgets container.show_all() ############################## # # Build stack components with boxes from glade # ############################## def build_stack(self,*args,**kwargs): stack=Gtk.Stack() for box_name in kwargs['boxes']: box=self.ctl.getobj(box_name) stack.add_named(box,box_name) stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) stack.set_transition_duration(400) return stack ############################## # # Change visualization elements from stacks # ############################## def change_stack(self,*args,**kwargs): # Get the stack and properties if 'stack' not in kwargs: stack=self.stack_main['obj'] history=self.stack_main['stack'] lstack=self.boxes_main visible_name = stack.get_visible_child_name() else: if type(kwargs['stack']) == type(str()): if kwargs['stack'] == 'main': stack=self.stack_main['obj'] history = self.stack_main['stack'] lstack = self.boxes_main visible_name = stack.get_visible_child_name() else: stack=self.stack_bottom['obj'] history = self.stack_bottom['stack'] lstack = self.boxes_bottom visible_name = stack.get_visible_child_name() else: stack=kwargs['stack'] visible_name = stack.get_visible_child_name() if visible_name in self.boxes_main: lstack=self.boxes_main else: lstack=self.boxes_bottom lindex=lstack.index(visible_name) next=lindex for to in args: try: if to == 'next': next += (lindex + 1) % len(lstack) elif to == 'prev': next += (lindex - 1) % len(lstack) elif to == 'back': next = lstack.index(history[1]) elif type(to) == type(int()): next += (lindex + to) % len(lstack) else: if to in lstack: next = lstack.index(to) except: pass stack.set_visible_child_name(lstack[next]) self.ctl.last_window = lstack[next] if to != 'back': history.insert(0,lstack[next]) else: del history[0] if len(history) > 10: del history[10] ############################## # # Change button label # ############################## def change_button_text(self,*args,**kwargs): if not check_args('button','text',**kwargs): return btn=self.ctl.getobj(kwargs['button']) btn.set_label(kwargs['text']) ############################## # # Update level bar & label # ############################## #@trace def update_status_bar(self,*args,**kwargs): bar=self.ctl.getobj('lvlbar') label=self.ctl.getobj('label_lvlbar') info=args[0] done=None while not done: time.sleep(0.1) bar.set_value(info['level']) label.set_text(info['msg']) done=info['done'] return ############################## # # Update with text any status bar # ############################## def set_txt_status_bars(self,*args,**kwargs): bars=[] contexts=[] for i in range(3): bars.insert(i,self.ctl.getobj('stbar'+str(i+1))) contexts.insert(i,bars[i].get_context_id(str(i))) if 'clean' in kwargs: for nbar in range(3): bars[nbar].remove_all(contexts[nbar]) return for i in range(3): if len(args) > i: bars[i].remove_all(contexts[i]) bars[i].push(contexts[i],args[i]) if 'bar_info' in kwargs: bar_info = kwargs['bar_info'] for num in bar_info: if num in ['0','1','2']: bars[int(num)].remove_all(contexts[int(num)]) bars[int(num)].push(contexts[int(num)],bar_info[num]) return ############################## # # Get values from entries or labels # ############################## def get_entry(self, *args, **kwargs): lout = [] for name in args: entry = self.ctl.getobj(name) lout.append(entry.get_text()) return lout ############################## # # Set values into entries or labels # ############################## def set_entry(self, *args, **kwargs): for entry in args: for name_entry in entry: value = entry[name_entry] self.ctl.getobj(name_entry).set_text(value) return def show_edit_mode(self,*args,**kwargs): if 'hide' in args: self.ctl.getobj('rbtn_move').hide() self.ctl.getobj('rbtn_copy').hide() elif 'show' in args: self.ctl.getobj('rbtn_move').show() self.ctl.getobj('rbtn_copy').show() else: self.ctl.getobj('rbtn_move').show() self.ctl.getobj('rbtn_copy').show() ############################## # # Event handler main class # ############################## class Handler(): def __init__(self,*args,**kwargs): check_args('controller',**kwargs) self.ctl=kwargs['controller'] def get_selected_rowid(self,*args,**kwargs): check_args('selected_obj','type',**kwargs) # # Get the selected element, returns elements id or none # treeview_selection = kwargs['selected_obj'] typearg = kwargs['type'] model, path = treeview_selection.get_selected_rows() # If nothing is selected abort actions if (len(path) < 1): return None selected = None if typearg == 'apps': selected = model[path][APPCOL.ID] if typearg == 'profiles': selected = model[path][PROFCOL.ID] return selected def destroy(self, *args, **kwargs): self.ctl.kill_all_pending_operations() Gtk.main_quit(*args) def delete_event(self,*args, **kwargs): for w in args: w.hide() return True def validate_clicked(self,*args,**kwargs): self.ctl.process(action='validation_init') def go_to_new_profile(self,*args,**kwargs): self.ctl.process(action='goto',to='new_profile') def go_to_edit_profile(self,*args,**kwargs): self.ctl.process(action='goto', to='edit_profile') def go_to_group_mapping(self,*args,**kwargs): self.ctl.process(action='goto', to='group_mapping') def close_window_and_go_back(self,*args,**kwargs): self.ctl.process(action='goto', to='back') def entry_filter_changed(self,*args,**kwargs): self.ctl.process(action='refilter_elements') def put_current_items(self,*args,**kwargs): self.ctl.process(action='put_current_items') def swap(self,*args,**kwargs): id=self.get_selected_rowid(selected_obj=args[0],type='apps') self.ctl.process(action='swap',id=id) def swap_local_elements(self,*args,**kwargs): pass # deprecated since v3 #self.ctl.process(action='swap_locals') def go_to_ask_remove_profile(self,*args,**kwargs): id = self.get_selected_rowid(selected_obj=args[0], type='profiles') if id != None: self.ctl.process(action='goto', to='ask_remove',id=id) def load_selected_profile(self,*args,**kwargs): id = self.get_selected_rowid(selected_obj=args[0], type='profiles') if id != None: self.ctl.process(action='goto',to='editing_profile',id=id) def confirm_yes(self,*args,**kwargs): id = self.get_selected_rowid(selected_obj=args[0], type='profiles') if id != None: self.ctl.process(action='goto',to='remove_confirmed',id=id) def profile_name_changed(self,*args,**kwargs): self.ctl.profile_name_changed = True def go_to_main_window(self,*args,**kwargs): self.ctl.process(action='goto',to='main_window') def cancel_profile(self,*args,**kwargs): self.ctl.process(action='cancel_profile') def combo_changed(self,*args,**kwargs): # # Omit handler when view is building to avoid fake checkings & processing # if not self.ctl.building_view: combo = args[0] label = args[1] textlabel = label.get_text() iter = combo.get_active_iter() model = combo.get_model() selected = model[iter][1] self.ctl.process(action='change_relations',group=textlabel,profile=selected) def go_to_filechooser(self,*args,**kwargs): self.ctl.process(action='goto', to='filechooser') def close_btn_filechooser(self,*args,**kwargs): self.ctl.process(action='close_filechooser') return True def selection_changed(self,*args,**kwargs): print('use file direct'+self.ctl.resource_previewed) self.ctl.process(action='selected_resource') def select_file(self,*args,**kwargs): print('use file '+self.ctl.resource_previewed) self.ctl.process(action='selected_resource') def selection_preview(self,*args,**kwargs): filechooser = args[0].get_preview_file() if filechooser: file = filechooser.get_path() ext = file.split('.') if ext[-1].lower() in ['png','jpg']: print('previewed '+file) self.ctl.process(action='selection_changed',file=file) def profile_accepted(self,*args,**kwargs): self.ctl.process(action='profile_accepted') def go_edit_plank_items(self, *args, **kwargs): self.ctl.process(action='edit_plank_items') def go_edit_desktop_items(self, *args, **kwargs): self.ctl.process(action='edit_desktop_items') def cancel_edit_elements(self, *args, **kwargs): self.ctl.process(action='cancel_edit_elements') def accept_edit_elements(self, *args, **kwargs): self.ctl.process(action='accept_edit_items') def remove_background(self, *args, **kwargs): self.ctl.process(action='remove_selected_background') def preview_background(self, *args, **kwargs): self.ctl.process(action='preview_background') def close_preview_window(self, *args, **kwargs): self.ctl.process(action='close_preview') def change_edit_mode(self,*args,**kwargs): name=[r for r in args[0].get_group() if r.get_active()][0].get_name() self.ctl.process(action='change_edit_mode',name=name) ############################## # # Liststore enumerations # ############################## class APPCOL(): VISIBLE = 0 TYPE = 1 ID = 2 ICON = 3 NAME = 4 DESCRIPTION = 5 SELECTED_DESKTOP = 6 SELECTED_PLANK = 7 BASENAME = 8 FULLPATH = 9 class PROFCOL(): VISIBLE = 0 ID = 1 NAME = 2 SELECTED = 3 ############################## # # Data model main class # ############################## class Model: def __init__(self, *args, **kwargs): check_args('controller',**kwargs) self.ctl=kwargs['controller'] self.user=None self.pwd=None self.is_validated=False #self.groups=None #self.profiles=None self.profile_list=[] self.group_list=[] self.cache_lists={} self.cache_info={'level':0.0,'msg':'','done':False} self.store_elements = self.ctl.getobj('liststore_elements') self.store_profiles = self.ctl.getobj('liststore_profiles') self.n_selected = 0 self.current_relations = {} self.init_sorted_filtering(type='all') pass def build_liststore(self,*args,**kwargs): # # Build liststore # check_args('type',**kwargs) typearg=kwargs['type'] self.clear_liststore(type=typearg) if typearg == 'apps': for t in ['local','system','desktop']: self.add_to_liststore(list=self.cache_lists[t], type=t) if 'profiles' == typearg: self.add_to_liststore(list=self.profile_list, type=typearg) ############################## # # Initialize liststore filters & sorting # ############################## def init_sorted_filtering(self, *args,**kwargs): if 'type' not in kwargs: type == 'all' else: type = kwargs['type'] if type not in ['elements','selected_plank','selected_desktop','all']: return False all = ['elements','selected_plank','selected_desktop'] if type == 'all': for t in all: self.init_sorted_filtering(type=t) return obj=self.ctl.getobj('sort_'+type) # Init sorting sort_column_in_model=APPCOL.NAME obj.set_sort_column_id(sort_column_in_model,Gtk.SortType.ASCENDING) model_filter=self.ctl.getobj('filter_'+type) # Init filters if type == 'selected_desktop': model_filter.set_visible_func(self.filter_func_selected_desktop) if type == 'selected_plank': model_filter.set_visible_func(self.filter_func_selected_plank) if type == 'elements': model_filter.set_visible_func(self.filter_func_apps) ############################## # # Empty liststores # ############################## def clear_liststore(self,*args,**kwargs): check_args('type', **kwargs) store=kwargs['type'] if store == 'apps': self.store_elements.clear() if 'profiles' == store: self.store_profiles.clear() ############################## # # Set selections into liststore # ############################## def set_liststore_selections(self,*args,**kwargs): check_args('list','type', **kwargs) if kwargs['type'] not in ['desktop','plank']: return None store = kwargs['type'] app_list = list(kwargs['list']) current_store = None id_element_selection = None id_element_name = None if store == 'desktop': current_store = self.store_elements id_element_selection = APPCOL.SELECTED_DESKTOP id_element_name = APPCOL.BASENAME if store == 'plank': current_store = self.store_elements id_element_selection = APPCOL.SELECTED_PLANK id_element_name = APPCOL.BASENAME if 'profiles' == store: current_store = self.store_profiles id_element_selection = PROFCOL.SELECTED id_element_name = PROFCOL.NAME # If name not exist, all selections are cleared for x in current_store: if x[id_element_name].lower() in app_list: x[id_element_selection] = True app_list.remove(x[id_element_name].lower()) elif x[id_element_selection]: x[id_element_selection] = False return def put_current_items(self, *args, **kwargs): current_desktop = self.get_liststore_selections(type='desktop') current_plank = self.get_liststore_selections(type='plank') for x in self.store_elements: if x[APPCOL.TYPE] == 'system': continue if x[APPCOL.TYPE] == 'desktop': if x[APPCOL.BASENAME].lower() not in current_desktop: x[APPCOL.SELECTED_DESKTOP] = True if x[APPCOL.TYPE] == 'local': if x[APPCOL.BASENAME].lower() not in current_plank: x[APPCOL.SELECTED_PLANK] = True ############################## # # Get current selections from liststore # ############################## def get_liststore_selections(self,*args,**kwargs): check_args('type',**kwargs) store=kwargs['type'] current_store = None if store == 'desktop': current_store = self.store_elements id_element_selection = APPCOL.SELECTED_DESKTOP id_element_target = APPCOL.BASENAME if store == 'plank': current_store = self.store_elements id_element_selection = APPCOL.SELECTED_PLANK id_element_target = APPCOL.BASENAME if store == 'profiles': current_store = self.store_profiles id_element_selection = PROFCOL.SELECTED id_element_target = PROFCOL.NAME applist = [] for x in current_store: if x[id_element_selection]: applist.append(x[id_element_target]) return applist ############################## # # Get current assigned group from profile or None # ############################## def get_group_from_profile(self,*args,**kwargs): check_args('nameprofile',**kwargs) name=kwargs['nameprofile'] name=name.strip() for p in self.profile_list: if p[0].lower() == name.lower(): return p[1] return None ############################## # # Add list with data into liststore # ############################## def add_to_liststore(self,*args,**kwargs): check_args('list','type',**kwargs) lapps=kwargs['list'] typearg=kwargs['type'] if typearg in ['local','system','desktop']: if typearg == 'local': i = 1 elif typearg == 'desktop': i = 1000 else: i = 10000 for item in lapps: # # Item # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string) # Liststore: # [0]visible(bool), [1]type(string), [2]id(int(1 0: self.window.set_txt_status_bars('', '', '{} {}({}) {}({}) {}({})'.format(_('Detected apps:'), _('Plank'), len(self.model.cache_lists['local']), _('System'), len(self.model.cache_lists['system']), _('Desktops'), len(self.model.cache_lists['desktop']))) self.helper.clean_status_bar(2) self.model.cache_info['msg'] = _('Ready!') self.model.cache_info['done']=True else: self.model.cache_info['msg'] = _("Unable to build complete cache...") # # Hide level bar # self.window.change_stack('next',stack='bottom') def debug_model(self,model): for i in model: li = list(i) print('{}'.format(li)) def check_profile_changes(self,*args,**kwargs): let_continue = True # def same_elements(la, lb): # for x in la: # if x not in lb: # return False # for x in lb: # if x not in la: # return False # return True # changes = [] # for x in self.initial_liststore: # if x in ['desktop', 'plank']: # ltest = self.model.get_liststore_selections(type=x) # if not same_elements(ltest, self.initial_liststore[x]): # changes.append('changed_'+x) # if len(ltest) == 0: # changes.append('empty_'+x) # elif x == 'background': # bg = self.window.get_entry('entry_desktop_background')[0] # bg = bg.strip() # if bg != self.initial_liststore[x][0]: # changes.append('changed_'+x) # if bg == '': # changes.append('empty_'+x) pname = self.window.get_entry('profile_name_entry')[0] pname = pname.strip() if pname == '': changes.append('empty_pname') if pname != self.initial_profile_name: changes.append('changed_pname') if self.user_is_editing == '_____new_profile_____': changes.append('new_profile') else: changes.append('editing_profile') # if 'empty_name' in changes: return False if 'new_profile' in changes: if 'changed_pname' in changes: if not self.model.check_profile_name(name=pname, type='available'): self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')}) self.window.change_stack('box_warning') let_continue = False elif 'editing_profile' in changes: if 'changed_pname' in changes: if not self.model.check_profile_name(name=pname, type='available'): self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')}) self.window.change_stack('box_warning') let_continue = False # else: # changes.append('need_remove') # # if 'changed_desktop' in changes: # desktops = self.model.get_liststore_selections(type='desktop') # else: # desktops = self.initial_liststore['desktop'] # if 'changed_plank' in changes: # planks = self.model.get_liststore_selections(type='plank') # else: # planks = self.initial_liststore['plank'] # if 'changed_background' in changes: # bg = self.window.get_entry('entry_desktop_background')[0] # else: # bg = self.initial_liststore['background'][0] #pname = self.window.get_entry('profile_name_entry')[0] #pname = pname.strip() if let_continue: desktops = self.model.get_liststore_selections(type='desktop') planks = self.model.get_liststore_selections(type='plank') bg = self.window.get_entry('entry_desktop_background')[0] desktop_pack = [] plank_pack = [] bg_pack = [] for x in desktops: for info in self.model.store_elements: if x == info[APPCOL.BASENAME]: content = None try: with open(info[APPCOL.FULLPATH],'rb') as fp: content = base64.b64encode(fp.read()) except Exception as e: continue desktop_pack.append([info[APPCOL.FULLPATH],info[APPCOL.BASENAME],content.decode()]) break for x in planks: for info in self.model.store_elements: if x == info[APPCOL.BASENAME]: content = None try: with open(info[APPCOL.FULLPATH], 'rb') as fp: content = base64.b64encode(fp.read()) except Exception as e: continue plank_pack.append([info[APPCOL.FULLPATH], info[APPCOL.BASENAME],content.decode()]) break content = None try: with open(bg, 'rb') as fp: content = base64.b64encode(fp.read()) bg_pack = [bg,content.decode()] except: pass grp = self.model.get_group_from_profile(nameprofile=pname) # applist = self.model.get_liststore_selections(type='apps') # grp = self.model.get_group_from_profile(nameprofile=profile_name) glist = [] if grp != None: glist.append(grp) # if self.edit_mode == 'copy': self.user_is_editing = '_copy_mode_' elif self.edit_mode == 'move': pass else: raise Exception('Error edit mode not set!') self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing,newname=pname,grouplist=glist,applist={'desktop':desktop_pack,'plank':plank_pack,'background':bg_pack}) #if 'need_remove' in changes: # self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing) return let_continue # # OLD CODE FROM GOTO(BACK) # # # # # Check if need to save something # # # changes_made = False # if self.last_window == 'box_profiles2': # if self.user_is_editing == 'new_profile': # if self.profile_name_changed or self.applist_changed: # pname = self.window.get_entry('profile_name_entry')[0] # # If modified and no changes are done really skip warning msg # if pname.strip() != self.initial_profile_name: # initial_profile_name always must be '' when is a new_profile # if not self.model.check_profile_name(name=pname, type='available'): # self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')}) # self.window.change_stack('box_warning') # changes_made = True # else: # # Store new profile (with valid name) # # Not allowed empty applist # applist = self.model.get_liststore_selections(type='desktop') # # if len(applist) > 0: # self.update_current_profile() # changes_made = True # elif self.user_is_editing != '': # # User is editing existent profile # if self.applist_changed and not self.profile_name_changed: # # Update current profile (self.user_is_editing) # applist = self.model.get_liststore_selections(type='apps') # if len(applist) > 0: # list_initial = self.initial_liststore # # check if anything has changed # applist_copy = list(applist) # for app in applist_copy: # if app in list_initial: # applist.remove(app) # list_initial.remove(app) # # skip update if modified list is the same initially created # if len(applist) > 0 or len(list_initial) > 0: # self.update_current_profile() # changes_made = True # # if not self.applist_changed and self.profile_name_changed: # # Rename current profile (self.user_is_editing) # newname = self.window.get_entry('entry_profile_name')[0] # # Skip if modified name is the same as initially created # if newname.strip() != self.initial_profile_name: # if not self.model.check_profile_name(name=newname, type='available'): # self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')}) # self.window.change_stack('box_warning') # changes_made = True # else: # self.helper.n4d_rename_profile(user=self.model.user, pwd=self.model.pwd, # old=self.user_is_editing, new=newname) # changes_made = True # # if self.applist_changed and self.profile_name_changed: # # create new profile & remove old profile (self.user_is_editing) # applist = self.model.get_liststore_selections(type='apps') # if len(applist) > 0: # list_initial = self.initial_liststore # # check if anything has changed # applist_copy = list(applist) # for app in applist_copy: # if app in list_initial: # applist.remove(app) # list_initial.remove(app) # # skip update if modified list is the same initially created # if len(applist) > 0 or len(list_initial) > 0: # newname = self.window.get_entry('entry_profile_name')[0] # if newname.strip() != self.initial_profile_name: # if not self.model.check_profile_name(name=newname, type='available'): # self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')}) # self.window.change_stack('box_warning') # else: # self.helper.n4d_rename_profile(user=self.model.user, pwd=self.model.pwd, # old=self.user_is_editing, new=newname) # changes_made = True # else: # # # # -Uncomment if it's desired to remove old profile when modifications of name and apps are done # # -If commented, new profile it's created making a copy of the current profile with other name # # and remove profile only it's possible from the remove menu, doing more explicitly the remove action # # # # self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing) # self.update_current_profile() # changes_made = True # # if changes_made: # return # # Receives events from handler # #@trace def process(self,*args,**kwargs): try: # # Check arguments and unify commands # Mandatory 'action' # Optional: 'result': if it comes from callback # 'to': changing windows from button menu # actions=[] try: check_args('action',**kwargs) except Exception as e: dbg(e) sys.exit(1) if type(kwargs['action']) == type(list()): actions.extend(kwargs['action']) else: actions.append(kwargs['action']) for op in args: if type(op) == type(list()): actions.extend(op) else: actions.append(op) for action in actions: if action == 'initialized': # # Run first actions # self.helper.do_call(self.window.update_status_bar,self.model.cache_info,None) self.helper.do_call(self.do_cache,{'action':'cache_completed'},self.process) elif action == 'cache_completed': if self.model.is_validated: # when we are validated and don't have cache, screen shows loading # when cache_completed but not validated, do nothing, validation will update window self.window.change_stack('box_main_btn') elif action == 'validation_init': # # Start the validation process # self.window.change_button_text(text='Checking',button='btn_validate') us,pw=self.window.get_entry('entry_usr','entry_pw') self.helper.n4d_validate(user=us,pwd=pw) elif action == 'validation_end': # # Callback from n4d validation process # try: check_args('result', **kwargs) except Exception as e: dbg('N4D fail check system!') sys.exit(1) result = kwargs['result'] self.validation_end(result) elif action == 'goto': # # Button from main menu pressed # self.goto(*args,**kwargs) elif action == 'edit_plank_items': self.current_selection = 'plank' self.model.refilter(type='all') self.backup_selections['plank'] = self.model.get_liststore_selections(type='plank') self.goto(to='edit_plank_items') elif action == 'edit_desktop_items': self.current_selection = 'desktop' self.model.refilter(type='all') self.backup_selections['desktop'] = self.model.get_liststore_selections(type='desktop') self.goto(to='edit_desktop_items') elif action == 'cancel_edit_elements': self.model.set_liststore_selections(type=self.current_selection,list=self.backup_selections[self.current_selection]) self.current_selection = None self.goto(to='back') elif action == 'accept_edit_items': self.current_selection = None self.goto(to='back') elif action == 'cancel_profile': self.model.build_liststore(type='apps') self.resource_previewed = None self.window.set_entry({'entry_desktop_background': ''}) self.window.set_entry({'profile_name_entry':''}) self.reset_memories() self.goto(to='main_window') elif action == 'profile_accepted': if self.check_profile_changes(): self.goto(to='main_window') elif action == 'put_current_items': self.model.put_current_items() self.model.refilter(type='all') elif action == 'preview_background': file_preview = self.window.get_entry('entry_desktop_background')[0] if os.path.isfile(file_preview): w = self.getobj('window_preview') w.add(Gtk.Image.new_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_scale(file_preview, 640, 480, True))) w.show_all() else: self.window.set_txt_status_bars('Unable to preview image {}'.format(file_preview)) self.helper.clean_status_bar(0) elif action == 'change_edit_mode': name = kwargs.get('name') if name == 'm': self.edit_mode = 'move' if name == 'c': self.edit_mode = 'copy' pass elif action == 'close_preview': w = self.getobj('window_preview') w.hide() elif action == 'close_filechooser': # # Cancel Filechooser from button # self.window.dialog_filechooser.hide() elif action == 'selected_resource': # # Resource previewed is selected (button accept or double click) # self.window.dialog_filechooser.hide() self.window.set_entry({'entry_desktop_background': self.resource_previewed}) elif action == 'selection_changed': # # Selected image to preview # resource = kwargs['file'] i=self.builder.get_object('image1') p=GdkPixbuf.Pixbuf.new_from_file_at_scale(resource,200,200,True) i.set_from_pixbuf(p) self.resource_previewed = resource elif action == 'remove_selected_background': self.resource_previewed = None self.window.set_entry({'entry_desktop_background': ''}) elif action == 'refilter_elements': ############################## # # refilter when filter entry has changed # ############################## self.model.refilter(type='elements') elif action == 'swap': ############################## # # change model & refilter when swaps elements # ############################## check_args('id',**kwargs) id=kwargs['id'] #self.applist_changed = True col = None if self.current_selection == 'desktop': col = APPCOL.SELECTED_DESKTOP if self.current_selection == 'plank': col = APPCOL.SELECTED_PLANK for e in self.model.store_elements: if e[APPCOL.ID]==id: e[col] = not e[col] break self.model.refilter(type='all') i,j=self.model.count_selected(type='all') self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected'))) # elif action == 'swap_locals': # ############################## # # # # change model for local elements # # # ############################## # self.applist_changed = True # for e in self.model.store_elements: # if e[APPCOL.TYPE]=='local': # e[APPCOL.SELECTED] = True # self.model.refilter(type='all') # i,j=self.model.count_selected(type='all') # self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected'))) elif action == 'set_groups': ############################## # # callback from n4d_get_system_groups # ############################## try: check_args('result',**kwargs) except Exception as e: dbg('N4D fail check system!') sys.exit(1) self.model.group_list = [] for g in [x['cn'][0] for x in kwargs['result']]: self.model.group_list.append(g) elif action == 'set_profiles': ############################## # # callback from n4d_get_profiles_stored # ############################## try: check_args('result',**kwargs) except Exception as e: dbg('N4D fail check system!') sys.exit(1) self.model.profile_list = [] for p in kwargs['result']: if type(p[1]) != type(str()) or type(p[0]) != type(str()): continue self.model.profile_list.append(p) for g in p[1].split(','): if g != '': self.model.current_relations[g]=p[0] self.model.sort_profiles() elif action == 'remove_profile_done': ############################## # # Callback from n4d_remove_profile # ############################## # Update information about existent profiles self.model.clear_liststore(type='profiles') self.window.profiles_need_refresh = True self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd) self.window.change_stack('box_main_btn') elif action == 'profile_is_updated': ############################## # # Callback from n4d_update_profile # ############################## # Update information about existent profiles self.model.clear_liststore(type='profiles') self.window.profiles_need_refresh = True self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd) check_args('name',**kwargs) name = kwargs['name'] # If this is a (single) callback from edit profiles if self.last_window != 'box_group_edit': check_args('newname', **kwargs) newname = kwargs['newname'] self.reset_memories() #self.window.set_txt_status_bars(name + _(' updated!')) self.window.set_txt_status_bars(newname + _(' updated!')) self.helper.clean_status_bar(0) self.window.change_stack('box_main_btn') else: # In this is a (multiple or single) callback from profile-group change relation check_args('status_bar',**kwargs) bar = kwargs['status_bar'] if bar == 'old': # old profile msg is always showed into first statusbar self.window.set_txt_status_bars(bar_info={'0': name + _(' updated!')}) self.helper.clean_status_bar(0) if bar == 'new': # old profile msg is always showed into second statusbar self.window.set_txt_status_bars(bar_info={'1': name + _(' updated!')}) self.helper.clean_status_bar(1) # elif action == 'profile_is_renamed': # ############################## # # # # Callback from n4d_rename_profile # # # ############################## # # # Update information about existent profiles # self.model.clear_liststore(type='profiles') # self.window.profiles_need_refresh = True # self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd) # # Force redo liststore from combobox from profile window, next time will rebuild the view # for boxname in self.window.group_profile_boxes: # self.window.group_profile_boxes[boxname][0].destroy() # self.window.group_profile_boxes[boxname][1].destroy() # self.window.group_profile_boxes = {} # self.reset_memories() # # Show msg into status bar # check_args('oldname', 'newname', **kwargs) # oldname = kwargs['oldname'] # newname = kwargs['newname'] # self.window.set_txt_status_bars(_('Updated profile from ')+oldname+_(' to ')+newname) # self.helper.clean_status_bar(0) # # Change user's view # self.window.change_stack('box_main_btn') elif action == 'load_profile_into_liststore': ############################## # # Callback from n4d_read_profile # ############################## check_args('name','result',**kwargs) profile_readed = kwargs['result'] workspace = self.helper.prepare_workspace(profile=profile_readed) if not workspace: self.window.set_txt_status_bars('{}'.format(_('Error reading profile'))) else: self.reset_memories() self.model.build_liststore(type='apps') self.initial_liststore = self.model.load_profile_into_liststore(name=kwargs['name'], workspace=workspace) i,j = self.model.count_selected(type='all') self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected'))) self.window.set_entry({'profile_name_entry':kwargs['name']}) self.initial_profile_name = kwargs['name'] self.user_is_editing = kwargs['name'] self.edit_mode = 'move' self.window.change_stack('box_profiles2') elif action == 'change_relations': check_args('group','profile',**kwargs) group = kwargs['group'] profile = kwargs['profile'] oldrelation = '' if group in self.model.current_relations: oldrelation = self.model.current_relations[group] # # Sanity checks about relations # if self.check_sanity(group=group,profile=profile): self.update_profile_relations(group=group, profile=profile, oldrelation=oldrelation) return except Exception as e: if not self.aborting_all_operations: raise Exception(e) else: sys.exit(1) ############################## # # Validation checking actions # ############################## def validation_end(self,*args,**kwargs): result = args[0] self.window.change_button_text(text='Validate', button='btn_validate') # # Check the completed validation # return_str = result.split(' ') if return_str[0].lower() == 'true' and return_str[1].lower() in ['admin', 'adm', 'admins', 'teachers']: # Validation is done and sucessful us, pw = self.window.get_entry('entry_usr', 'entry_pw') self.model.user = us self.model.pwd = pw self.model.is_validated = True # # Get information about existent groups # self.helper.n4d_get_system_groups(user=us,pwd=pw) # # Get information about existent profiles # self.helper.n4d_get_profiles_stored(user=us,pwd=pw) # # Update content window # if self.model.cache_info['done']: self.window.change_stack('box_main_btn') else: self.window.change_stack('box_loading') else: # Validation is failed self.window.set_entry({'entry_usr': '', 'entry_pw': ''}) self.getobj('img_emoji').set_from_icon_name('face-crying', 100) ############################## # # Change window # ############################## def goto(self,*args,**kwargs): check_args('to', **kwargs) to = kwargs['to'] if to == 'main_window': self.reset_memories() self.window.set_txt_status_bars('') self.window.change_stack('box_main_btn') if to == 'new_profile': self.window.set_entry({'profile_name_entry':'','entry_desktop_background':''}) # TODO: Chango to reset memories self.resource_previewed = None self.initial_profile_name = '' self.initial_liststore = {} self.model.build_liststore(type='apps') self.user_is_editing = '_____new_profile_____' self.window.show_edit_mode('hide') self.edit_mode = 'copy' self.window.change_stack('box_profiles2') if to == 'filechooser': self.window.dialog_filechooser.set_current_folder('/usr/share/backgrounds') self.window.dialog_filechooser.show_all() if to == 'edit_profile': self.model.build_liststore(type='profiles') self.window.show_edit_mode('show') self.window.change_stack('box_pselector') if to == 'edit_plank_items': self.model.change_model_selected(type='plank') self.window.change_stack('box_edit_elements') if to == 'edit_desktop_items': self.model.change_model_selected(type='desktop') self.window.change_stack('box_edit_elements') if to == 'group_mapping': #prevent trigger combo actions self.building_view = True self.window.build_group_profile_window(groups=self.model.group_list,profiles=self.model.profile_list) self.building_view = False self.window.change_stack('box_group_edit') if to == 'ask_remove': check_args('id', **kwargs) id = kwargs['id'] name_profile = self.model.profile_list[id][0] txt="{} '{}'?".format(_('Are you sure to remove the profile'),name_profile) self.window.set_entry({'lbl_msg_dialog':txt}) self.window.change_stack('box_dialog') if to == 'editing_profile': check_args('id', **kwargs) id = kwargs['id'] name_profile=self.model.profile_list[id][0] self.helper.n4d_read_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile) if to == 'remove_confirmed': check_args('id', **kwargs) id = kwargs['id'] name_profile = self.model.profile_list[id][0] self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile) if to == 'back': # if nothing is saved do normal go back self.model.n_selected = 0 self.window.set_txt_status_bars('','') self.window.change_stack('back') return # ############################## # # # # Read store & create or update a profile # # # ############################## # def update_current_profile(self,*args,**kwargs): # profile_name = self.window.get_entry('entry_profile_name')[0] # #if self.user_is_editing == 'new_profile' and not self.model.check_profile_name(name=profile_name,type='available'): # # self.window.set_entry({'lbl_msg_warning':_('Profile name not valid')}) # # self.window.change_stack('box_warning') # # self.reset_memories() # # return # applist = self.model.get_liststore_selections(type='apps') # grp = self.model.get_group_from_profile(nameprofile=profile_name) # glist = [] # if grp != None: # glist.append(grp) # # self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=profile_name,grouplist=glist,applist=applist) # return ############################## # # Update profile assigning one group when changed on combo and it's different # ############################## def update_profile_relations(self,*args,**kwargs): try: # # First call acts as a caller from N4D method setting itself as a callback method # if haven't group & profile parameters, method is doing callback with 'result' parameter # check_args('group', 'profile','oldrelation', **kwargs) group = kwargs['group'] profile_selected = kwargs['profile'] old_profile = kwargs['oldrelation'] # # Check if it's needed to update old profile removing relation # if self.model.check_profile_name(name=old_profile,type='valid'): # # need to kwown the current apps and other groups applied from profile before updating # oldgroups = [] for p in self.model.profile_list: pname = p[0] pgrps = p[1] if pname == old_profile: for g in pgrps.split(','): if g != group and g != '': oldgroups.append(g) self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=old_profile, noprocess=self.update_profile_relations, extraparam={'grouplist': oldgroups,'status_bar':'old'}) if self.model.check_profile_name(name=profile_selected,type='valid'): # # need to kwown the current apps and other groups applied from profile before updating # oldgroups = [] for p in self.model.profile_list: pname = p[0] pgrps = p[1] if pname == profile_selected: for g in pgrps.split(','): if g != group and g != '': oldgroups.append(g) newglist = [group] newglist.extend(oldgroups) self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=profile_selected, noprocess=self.update_profile_relations, extraparam={'grouplist': newglist,'status_bar':'new'}) except Exception as e: # # Second call of this function, when callback from n4d_read_profile is done, 'result' is set into parameters with list applications of profile # check_args('grouplist','result','name','status_bar',**kwargs) profile_readed = kwargs['result'] workspace = self.helper.prepare_workspace(profile=profile_readed) grouplist = kwargs['grouplist'] name = kwargs['name'] status_bar = kwargs['status_bar'] self.helper.n4d_update_profile_groups(user=self.model.user,pwd=self.model.pwd,name=name,grouplist=grouplist, status_bar=status_bar) # After this call return to normal callback and will update profile list with new relations return ############################## # # Sanity logic about relations # ############################## def check_sanity(self,*args,**kwargs): check_args('group','profile',**kwargs) group = kwargs['group'] profile = kwargs['profile'] if not group: return False # Existence flags ret_gr = False for g in self.model.group_list: if g == group: # Group must exist ret_gr = True break for p in self.model.profile_list: pname = p[0] pgroup = p[1] for g in pgroup.split(','): if group == g: # Fail if already relationed if pname == profile: return False return ret_gr ############################## # # Main program # ############################## GObject.threads_init() if __name__ == "__main__": ctl = Controller(localpath=os.path.dirname(os.path.realpath(__file__))+'/../lib/syncer-plank') #ctl = Controller(localpath='/usr/lib/syncer-plank') sys.exit(Gtk.main())