Changeset 4303


Ignore:
Timestamp:
Apr 19, 2017, 1:16:15 PM (2 years ago)
Author:
mabarracus
Message:

Updated UI
Add i18n localization
Add desktop file & icon

Location:
syncer-plank/trunk/fuentes
Files:
38 added
1 deleted
3 edited

Legend:

Unmodified
Added
Removed
  • syncer-plank/trunk/fuentes/debian/changelog

    r4158 r4303  
     1syncer-plank (0.3) xenial; urgency=medium
     2
     3  * Updated UI
     4  * Add i18n localization
     5  * Add desktop file & icon
     6
     7 -- M.Angel Juan <m.angel.juan@gmail.com>  Wed, 19 Apr 2017 13:14:54 +0200
     8
    19syncer-plank (0.2-1) xenial; urgency=medium
    210
  • syncer-plank/trunk/fuentes/syncer-plank.install/usr/bin/syncer-plank-gui

    r4083 r4303  
    11#!/usr/bin/env python3
    2 
    3 # import logging
    4 # from colorlog import ColoredFormatter
    5 # from functools import wraps
    6 # import time
     2###################################################
     3#                                                 #
     4# User interface for syncer-plank                 #
     5# Author: M.Angel Juan <m.angel.juan@gmail.com>   #
     6# License: GPLv3                                  #
     7#                                                 #
     8###################################################
     9
     10##############################
    711#
    8 # def setup_logger():
    9 #     """Return a logger with a default ColoredFormatter."""
    10 #     formatter = ColoredFormatter(
    11 #         "(%(threadName)-9s) %(log_color)s%(levelname)-8s%(reset)s %(message_log_color)s%(message)s",
    12 #         datefmt=None,
    13 #         reset=True,
    14 #         log_colors={
    15 #             'DEBUG': 'cyan',
    16 #             'INFO': 'green',
    17 #             'WARNING': 'yellow',
    18 #             'ERROR': 'red',
    19 #             'CRITICAL': 'red',
    20 #         },
    21 #         secondary_log_colors={
    22 #             'message': {
    23 #                 'ERROR': 'red',
    24 #                 'CRITICAL': 'red',
    25 #                 'DEBUG': 'yellow'
    26 #             }
    27 #         },
    28 #         style='%'
    29 #     )
     12# Logging & debug helper functions
    3013#
    31 #     logger = logging.getLogger(__name__)
    32 #     handler = logging.StreamHandler()
    33 #     handler.setFormatter(formatter)
    34 #     logger.addHandler(handler)
    35 #     logger.setLevel(logging.DEBUG)
     14##############################
     15import logging
     16from colorlog import ColoredFormatter
     17from functools import wraps
     18import time
     19
     20def setup_logger():
     21    """Return a logger with a default ColoredFormatter."""
     22    formatter = ColoredFormatter(
     23        "(%(threadName)-9s) %(log_color)s%(levelname)-8s%(reset)s %(message_log_color)s%(message)s",
     24        datefmt=None,
     25        reset=True,
     26        log_colors={
     27            'DEBUG': 'cyan',
     28            'INFO': 'green',
     29            'WARNING': 'yellow',
     30            'ERROR': 'red',
     31            'CRITICAL': 'red',
     32        },
     33        secondary_log_colors={
     34            'message': {
     35                'ERROR': 'red',
     36                'CRITICAL': 'red',
     37                'DEBUG': 'yellow'
     38            }
     39        },
     40        style='%'
     41    )
     42
     43    logger = logging.getLogger(__name__)
     44    handler = logging.StreamHandler()
     45    handler.setFormatter(formatter)
     46    logger.addHandler(handler)
     47    logger.setLevel(logging.DEBUG)
     48
     49    return logger
     50
     51def trace(func):
     52    """Tracing wrapper to log when function enter/exit happens.
     53    :param func: Function to wrap
     54    :type func: callable
     55    """
     56    @wraps(func)
     57    def wrapper(*args, **kwargs):
     58        t=time.time()
     59        strdebug='{0:0.3f} Start {1!r} '. format(t-start_time,func.__name__)
     60        #strdebug+='KWARGS={} ARGS={}'.format(kwargs,args)
     61        #
     62        # Filter parameters
     63        #
     64        out={}
     65        for x in kwargs:
     66            text=str(kwargs[x])
     67            if len(text) < 30 and 'object at' not in text:
     68                out[x]=str(kwargs[x])
     69            else:
     70                out[x]='Skipped'
     71        i=0
     72        for x in args:
     73            text=str(x)
     74            if len(text) < 30 and 'object at' not in text:
     75                out['arg' + str(i)] = x
     76            else:
     77                out['arg' + str(i)] = 'Skipped'
     78            i += 1
     79        strdebug += 'Parameters: {}'.format(out)
     80        logger.debug(strdebug)
     81        result = func(*args, **kwargs)
     82        logger.debug('+{0:0.3f} End {1!r}'. format(time.time()-t,func.__name__))
     83        return result
     84    return wrapper
     85
     86start_time=time.time()
     87logger = setup_logger()
     88
     89def dbg(*args,**kwargs):
     90    enable=False
     91    if enable:
     92        for x in args:
     93            print(str(x))
     94
     95def check_args(*args,**kwargs):
     96    for a in args:
     97        if a not in kwargs:
     98            str='{} not in kwargs!'.format(a)
     99            raise Exception(str)
     100    return True
     101
     102import ctypes
     103
     104def terminate_thread(thread):
     105    """Terminates a python thread from another thread.
     106
     107    :param thread: a threading.Thread instance
     108    """
     109    if not thread.isAlive():
     110        return
     111
     112    exc = ctypes.py_object(SystemExit)
     113    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
     114        ctypes.c_long(thread.ident), exc)
     115    if res == 0:
     116        raise ValueError("nonexistent thread id")
     117    elif res > 1:
     118        # """if it returns a number greater than one, you're in trouble,
     119        # and you should call it again with exc=NULL to revert the effect"""
     120        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
     121        raise SystemError("PyThreadState_SetAsyncExc failed")
     122
     123##############################
    36124#
    37 #     return logger
     125# Imports section
    38126#
    39 # def trace(func):
    40 #     """Tracing wrapper to log when function enter/exit happens.
    41 #     :param func: Function to wrap
    42 #     :type func: callable
    43 #     """
    44 #     @wraps(func)
    45 #     def wrapper(*args, **kwargs):
    46 #         t=time.time()
    47 #         logger.debug('{0:0.2f} Start {1!r}'. format(t-start_time,func.__name__))
    48 #         #logger.debug('ARGS={} KWARGS={}'.format(args,kwargs))
    49 #         result = func(*args, **kwargs)
    50 #         logger.debug('+{0:0.2f} End {1!r}'. format(time.time()-t,func.__name__))
    51 #         return result
    52 #     return wrapper
    53 #
    54 # start_time=time.time()
    55 # logger = setup_logger()
     127##############################
    56128
    57129import sys,os
     
    59131import re
    60132import threading
     133import time
    61134import glob
     135import xmlrpc.client as x
     136import ssl
    62137
    63138gi.require_version('Gtk','3.0')
    64139from gi.repository import Gtk,GdkPixbuf,GObject,Gdk,GLib,Gio
    65140
    66 import xmlrpc.client as x
    67 import ssl
    68 
    69 GObject.threads_init()
    70 
    71 class Handler():
    72     def __init__(self,model,window):
    73 
    74         # for old python instead context=: transport=xmlrpclib.SafeTransport(use_datetime=True, context=context))
    75 
    76         self.model=model
    77         self.window=window
    78 
    79         self.dlgselprofile = self.window.builder.get_object("dlg_sel_profile")
    80         self.btn_user = self.window.builder.get_object("entry1")
    81         self.btn_pass = self.window.builder.get_object("entry2")
    82         self.txt_login = self.window.builder.get_object("btn_validate")
    83 
    84 
    85     def destroy(self,*args,**kwargs):
    86         Gtk.main_quit(*args)
    87 
    88     def onDeleteWindow(self,*args,**kwargs):
    89         Gtk.main_quit(*args)
    90 
    91     def delete_event(self,widget,*args,**kwargs):
    92         widget.hide()
    93         return True
    94 
    95     def show_dialog_add(self,*args,**kwargs):
    96         self.window.dlgadd.show_all()
    97 
    98     def click(self,widget,path,*args,**kwargs):
    99         name=widget[path][3]
    100         for x in self.model:
    101             if x[3] == name and x[5] == self.window.model_used:
    102                 x[1] = not x[1]
    103                 iditem=x[0]
    104                 if x[1]:
    105                     self.window.selected.append(iditem)
    106                 else:
    107                     self.window.selected.remove(iditem)
    108                 break
    109         self.window.update_status_bar()
    110 
    111     def do_system_apps(self,*args,**kwargs):
    112         self.open_add_window(model_used='system')
    113 
    114     def do_current_elements(self,*args,**kwargs):
    115         self.open_add_window(model_used='local')
    116 
    117     def go_to(self,*args,**kwargs):
    118         self.window.search = ''
    119         self.window.searchentry.set_text(self.window.search)
    120         if self.window.model_used == 'local':
    121             self.window.btn_goto.set_label('Ver apps sistema')
    122             self.do_system_apps()
    123         else:
    124             self.window.btn_goto.set_label('Ver apps plank')
    125             self.do_current_elements()
    126 
    127     def open_add_window(self,*args,**kwargs):
    128         self.window.model_used = kwargs['model_used']
    129         self.window.dlgadd.hide()
    130         self.window.window_general.hide()
    131         if not self.window.loaded_cache_system or not self.window.loaded_cache_local:
    132             self.window.load.show_all()
    133         self.window.refilter()
    134         self.window.update_status_bar()
    135         GObject.timeout_add(50, self.window.check_updated_cache)
    136 
    137     def activate_search(self,widget,*args,**kwargs):
    138         if len(args) > 0 and Gtk.EntryIconPosition.SECONDARY is args[0]:
    139             self.window.search=''
    140         else:
    141             self.window.search=widget.get_text()
    142         self.window.refilter()
    143 
    144     def go_back(self,*args,**kwargs):
    145         self.window.model_used=None
    146         self.window.window.hide()
    147         self.update_general_view()
    148         self.window.window_general.show_all()
    149         bar = self.window.statusbar
    150         bar.remove_all(self.window.statusbar_context)
    151         self.window.search=''
    152         self.window.searchentry.set_text(self.window.search)
    153         GObject.timeout_add(50, self.window.check_updated_cache)
    154 
    155     def select_all(self,model,*args,**kwargs):
    156         for row in [ r.path for r in self.model if r[5] == self.window.model_used]:
    157             self.model[row][1]=True
    158             self.window.selected.append(self.model[row][0])
    159         self.window.update_status_bar()
    160 
    161     def select_none(self,model,*args,**kwargs):
    162         for row in [ r.path for r in self.model if r[5] == self.window.model_used]:
    163             self.model[row][1]=False
    164             if self.model[row][0] in self.window.selected:
    165                 self.window.selected.remove(self.model[row][0])
    166         self.window.update_status_bar()
    167 
    168     def confirm_dialog_ok(self,*args,**kwargs):
    169         self.dialog_err.hide()
    170         self.window.window.grab_focus()
    171 
    172     def apply_selection(self,*args,**kwargs):
    173         if(len(self.window.selected) == 0):
    174             self.dialog_err=self.window.builder.get_object("dialog_confirm")
    175             self.dialog_msg=self.window.builder.get_object("label_confirm")
    176             b=self.window.builder.get_object("btn_confirm_ok")
    177             b.show()
    178             for btn in ['btn_confirm_sure','btn_confirm_cancel']:
    179                 b=self.window.builder.get_object(btn)
    180                 b.hide()
    181             self.dialog_msg.set_text("No elements selected, need to select any element !!!")
    182             self.dialog_err.show_all()
    183             return None
    184 
    185         if (len(self.window.selected) > 10):
    186             self.dialog_err = self.window.builder.get_object("dialog_confirm")
    187             self.dialog_msg = self.window.builder.get_object("label_confirm")
    188             b = self.window.builder.get_object("btn_confirm_ok")
    189             b.hide()
    190             for btn in ['btn_confirm_sure','btn_confirm_cancel']:
    191                 b=self.window.builder.get_object(btn)
    192                 b.show()
    193             self.dialog_msg.set_text("You have selected more than 10 elements, pannel visualization may be problematic !!!")
    194             self.dialog_err.show_all()
    195             self.dialog_err.backfrom='create_profiles'
    196             return None
    197 
    198         self.apply_selection_sure()
    199 
    200 
    201     def edit_window_cancel(self,window,*args,**kwargs):
    202             window.hide()
    203             self.window.window.grab_focus()
    204 
    205     def apply_selection_sure(self,*args,**kwargs):
    206         if hasattr(self,'dialog_err'):
    207             self.dialog_err.hide()
    208 
    209         apps=[]
    210         for a in self.window.selected:
    211             for s in self.window.store:
    212                 if s[0] == a:
    213                     apps.append(s[3])
    214 
    215         self.show_profile_window(apps, self.window.groups,self.window.preselected_profile_name, self.window.profiles)
    216 
    217         return
    218 
    219     def dialog_close(self,*args,**kwargs):
    220         self.dialog_err.hide()
    221 
    222     def do_check_password(self,*args,**kwargs):
    223         us=self.btn_user.get_text()
    224         pw=self.btn_pass.get_text()
    225         d={'args':(us,pw)}
    226         login_func = lambda x: x.update({'result': self.window.n4d.login(x.get('args'),'Golem',x.get('args'))})
    227         self.txt_login.set_label('Checking')
    228         self.window.do_call(login_func, d, self.checked_password)
    229         return
    230 
    231     def update_general_view(self,*args,**kwargs):
    232         profiles = self.window.get_profiles_stored()
    233         groups = self.window.get_system_groups()
    234         profilestore = self.window.builder.get_object('liststore5')
    235         profilestore.clear()
    236         groupstore = self.window.builder.get_object('liststore2')
    237         groupstore.clear()
    238         i = 0
    239         d = {}
    240         for p in profiles:
    241             profilestore.append((i, p[0]))
    242             for g in p[1].split(','):
    243                 d[g] = p[0]
    244             i += 1
    245         i = 0
    246         for g in groups:
    247             if g in d:
    248                 strgrp = g + ' [' + d[g] + ']'
    249             else:
    250                 strgrp = g + ' []'
    251             groupstore.append((i, strgrp))
    252             i += 1
    253         self.window.loginbox.hide()
    254         self.window.profilebox.show()
    255         self.window.statusbar.show()
    256         self.window.statusbar2.show()
    257         return
    258 
    259     def checked_password(self,*args,**kwargs):
    260         result=kwargs['result'].split(' ')
    261         if result[0].upper() == 'TRUE':
    262             if result[1].lower() in ['adm','admins','teachers','admin']:
    263                 self.window.user, self.window.pwd = kwargs['args']
    264                 self.update_general_view()
    265                 self.window.imagelogin.set_from_icon_name('face-glasses', 100)
    266 
    267             self.txt_login.set_label('Validate')
    268             return
    269 
    270 
    271         self.txt_login.set_label('Validate')
    272         self.window.imagelogin.set_from_icon_name('face-crying',100)
    273         self.btn_pass.set_text('')
    274         self.btn_user.set_text('')
    275         return
    276 
    277     def save_file(self,window,*args,**kwargs):
    278         groupstore = self.window.builder.get_object('liststore3')
    279         appstore=self.window.builder.get_object('liststore4')
    280         profile_entry=self.window.builder.get_object('profile_entry_name')
    281         profile_name=profile_entry.get_text()
    282         statusbar = self.window.builder.get_object("statusbar2")
    283         context = statusbar.get_context_id('Error')
    284         if profile_name == '':
    285             statusbar.remove_all(context)
    286             statusbar.push(context, 'Profile name is needed !!!')
    287             return None
    288         else:
    289             if profile_entry.get_editable() and profile_name in [ p[0] for p in self.window.profiles]:
    290                 statusbar.remove_all(context)
    291                 statusbar.push(context, 'Profile name not available !!!')
    292                 return None
    293 
    294         glist_enabled=[]
    295         #EJEMPLO USO COMBO
    296         #combo_grp=self.window.builder.get_object("combobox1")
    297         #idx=combo_grp.get_active()
    298         #model=combo_grp.get_model()
    299         #print(model[idx][0])
    300         app_enabled=[]
    301         for a in appstore:
    302             app_enabled.append(a[0])
    303         for g in groupstore:
    304             if g[1] and g[3]:
    305                 glist_enabled.append(g[2])
    306         status=self.window.update_profile(profile_name,glist_enabled,app_enabled)
    307         self.window.selected = []
    308         for x in self.window.store:
    309             if x[1]:
    310                 x[1]=False
    311         if  self.window.preselected_profile_name:
    312             self.window.preselected_profile_name = None
    313         self.go_back()
    314         window.hide()
    315 
    316     def do_edit_profiles(self,*args,**kwargs):
    317         profiles = self.window.builder.get_object("liststore6")
    318         profiles.clear()
    319 
    320         i=0
    321         for x in self.window.profiles:
    322            profiles.append((i, x[0]))
    323 
    324         self.dlgselprofile.backfrom="edit_profiles"
    325         self.dlgselprofile.show_all()
    326     def remove_profile(self,*args,**kwargs):
    327         profiles = self.window.builder.get_object("liststore6")
    328         profiles.clear()
    329 
    330         i = 0
    331         for x in self.window.profiles:
    332             profiles.append((i, x[0]))
    333 
    334         self.dlgselprofile.backfrom = "remove_profiles"
    335         self.dlgselprofile.show_all()
    336     def unselect(self,*args):
    337         treeview = self.window.builder.get_object('treeview2')
    338         selection = treeview.get_selection()
    339         selection.unselect_all()
    340 
    341     def show_profile_window(self,apps,groups,profile_name,list_profile,*args,**kwargs):
    342         appstore = self.window.builder.get_object('liststore4')
    343         appstore.clear()
    344         for a in apps:
    345             appstore.append((a,))
    346         groupstore = self.window.builder.get_object('liststore3')
    347         groupstore.clear()
    348         groups_applied = []
    349         groups_banned = []
    350         for p in list_profile:
    351             if p[0] == profile_name:
    352                 for g in p[1].split(','):
    353                     groups_applied.append(g)
    354             else:
    355                 for g in p[1].split(','):
    356                     groups_banned.append(g)
    357         i = 0
    358         for g in groups:
    359             enabled = True
    360             if g in groups_banned:
    361                 enabled = False
    362             if g in groups_applied:
    363                 groupstore.append((i, True, g, enabled))
    364             else:
    365                 groupstore.append((i, False, g, enabled))
    366             i += 1
    367         edit_window = self.window.builder.get_object("window_edit_profile")
    368         edit_window.appstore_copy=appstore
    369         profile_entry = self.window.builder.get_object("profile_entry_name")
    370         b = self.window.builder.get_object("btn_rename_profile")
    371         b2= self.window.builder.get_object("btn_add_apps")
    372         if profile_name or self.window.preselected_profile_name:
    373             profile_entry.set_text(profile_name)
    374             b.show()
    375             b2.show()
    376         else:
    377             profile_entry.set_text('')
    378             profile_entry.set_editable(True)
    379             b.hide()
    380         edit_window.show_all()
    381 
    382     def profile_selected(self,widget,*args,**kwargs):
    383         self.dlgselprofile.hide()
    384         model,path=self.window.builder.get_object('profile_selection').get_selected_rows()
    385         if len(path) < 1:
    386             return
    387         selected_profile=model[path][1]
    388 
    389         if self.dlgselprofile.backfrom=='edit_profiles':
    390             d = {'args': [(self.window.user, self.window.pwd), selected_profile]}
    391             get_profile = lambda x: x.update(
    392                 {'result': self.window.n4d.read_profile(x.get('args')[0], 'PlankSync', x.get('args')[1])})
    393             apps = self.window.do_call(get_profile, d, None, block=True)
    394             self.window.preselected_profile_name=selected_profile
    395             self.show_profile_window(apps,self.window.groups,selected_profile,self.window.profiles)
    396 
    397         if self.dlgselprofile.backfrom=='remove_profiles':
    398             dialog = self.window.builder.get_object("dialog_confirm")
    399             dialog_msg = self.window.builder.get_object("label_confirm")
    400             dialog_msg.set_text('Are you sure to remove profile \'{}\''.format(selected_profile))
    401             dialog.backfrom='remove_profiles'
    402             dialog.show_all()
    403             for x in ['btn_confirm_sure','btn_confirm_cancel']:
    404                 btn=self.window.builder.get_object(x)
    405                 btn.show()
    406 
    407 
    408     def check_profile_assign(self,widget,path,*args,**kwargs):
    409         groupstore = self.window.builder.get_object('liststore3')
    410         id_clicked=widget[path][0]
    411         groupstore[id_clicked][1] = not groupstore[id_clicked][1]
    412 
    413     def confirm_dialog_cancel(self, *args,**kwargs):
    414         dialog = self.window.builder.get_object("dialog_confirm")
    415         dialog.hide()
    416 
    417     def confirm_dialog_sure(self,*args,**kwargs):
    418         dialog = self.window.builder.get_object("dialog_confirm")
    419         dialog.hide()
    420         if dialog.backfrom == 'remove_profiles':
    421             self.dlgselprofile.hide()
    422             self.do_remove_profile()
    423 
    424 
    425         if dialog.backfrom == 'create_profiles':
    426             self.apply_selection_sure()
    427 
    428     def rename_profile(self,*args,**kwargs):
    429         d=self.window.builder.get_object("dialog_rename")
    430         msgbox = self.window.builder.get_object("rename_msg")
    431         entry = self.window.builder.get_object("entry_rename")
    432         entry.set_text('')
    433         msgbox.set_text('')
    434         d.show_all()
    435 
    436     def do_rename(self,window,*args,**kwargs):
    437         entry = self.window.builder.get_object("entry_rename")
    438         oldname = self.window.builder.get_object("profile_entry_name")
    439         msgbox = self.window.builder.get_object("rename_msg")
    440         msgbox.set_text('')
    441         text=entry.get_text()
    442         if text == '':
    443             msgbox.set_text('New name can\'t be empty')
    444             return
    445         for p in self.window.profiles:
    446             if text == p[0]:
    447                 msgbox.set_text('New name not available')
    448                 return
    449         window.hide()
    450         parent=window.get_transient_for()
    451         status=self.window.rename_profile(oldname.get_text(),text)
    452         if not status:
    453             msgbox.set_text('Error renaming')
    454             return
    455         parent.hide()
    456         self.update_general_view()
    457 
    458     def do_remove_profile(self):
    459         model, path = self.window.builder.get_object('profile_selection').get_selected_rows()
    460         if len(path) < 1:
    461             return
    462         selected_profile = model[path][1]
    463         status = self.window.remove_profile(selected_profile)
    464         self.update_general_view()
    465 
    466     def add_more_apps(self,*args,**kwargs):
    467         edit_window = self.window.builder.get_object("window_edit_profile")
    468         edit_window.hide()
    469         for row in [r for r in self.model if r[1] == True]:
    470             row[1]=False
    471         self.window.selected = []
    472         appstore = self.window.builder.get_object('liststore4')
    473         self.window.preselected_apps=[]
    474         for x in appstore:
    475             self.window.preselected_apps.append(x)
    476         name = self.window.builder.get_object("profile_entry_name")
    477         self.window.preselected_profile_name=name.get_text()
    478         GObject.timeout_add(50, self.window.check_updated_cache)
    479 
    480 class MainWindow:
    481 
     141#
     142# i18n
     143#
     144import gettext
     145from gettext import gettext as _
     146gettext.bindtextdomain('syncer_plank_gui','/usr/share/locale')
     147gettext.textdomain('syncer_plank_gui')
     148
     149##############################
     150#
     151# Helper functions, threading & n4d mainly
     152#
     153##############################
     154class Helper:
     155    #
     156    # Helper initialization
     157    #
    482158    def __init__(self,*args,**kwargs):
    483         if kwargs['localpath']:
    484             self.gladefile = kwargs['localpath']+'/'+"gui.glade"
    485         self.builder = Gtk.Builder()
    486         self.builder.add_from_file(self.gladefile)
    487 
    488         self.user=None
    489         self.pwd=None
    490         self.groups=None
    491         self.profiles=None
    492         self.window = self.builder.get_object("window1")
    493         self.btn_goto = self.builder.get_object('btn_go_to')
    494         self.load = self.builder.get_object("window2")
    495         self.window_general = self.builder.get_object("window_general")
    496         self.store = self.builder.get_object("liststore_apps")
    497         self.store.clear()
    498         self.progressbar = self.builder.get_object("progressbar1")
    499         self.progressbar2 = self.builder.get_object("progressbar2")
    500         self.filter=self.builder.get_object("treemodelfilter_apps")
    501         self.sort=self.builder.get_object("treemodelsort_apps")
    502         self.statusbar=self.builder.get_object("statusbar1")
    503         self.statusbar2 = self.builder.get_object("statusbar3")
    504         self.statusbar_context=self.statusbar.get_context_id('Syncer')
    505         self.searchentry=self.builder.get_object("searchentry1")
    506         self.search=''
    507         self.filter.set_visible_func(self.visible_func)
    508         self.sort.set_sort_column_id(3,Gtk.SortType.ASCENDING)
    509 
    510         self.files_cache=None
    511         self.local_icon_cache=None
    512         self.system_icon_cache=None
    513         self.updated_cache_local=False
    514         self.updated_cache_system=False
    515         self.loaded_cache_local=None
    516         self.loaded_cache_system=None
    517         self.preselected_apps=[]
    518         self.preselected_profile_name=[]
    519         self.model_used=None
    520         self.progress_text=''
    521         self.progress_level=0.0
    522         self.selected=[]
    523         self.loginbox = self.builder.get_object("box8")
    524         self.buttonbox= self.builder.get_object("button_box")
    525         self.profilebox= self.builder.get_object('profile_box')
    526         self.imagelogin=self.builder.get_object("image5")
    527 
    528         self.dlgadd = self.builder.get_object("dlg_add")
    529         #self.dlgadd.connect("delete_event",self.delete_event_signal)
    530 
    531         self.builder.connect_signals(Handler(self.store, self))
    532         self.loginbox.show()
    533         self.window_general.show_all()
    534 
    535         GObject.idle_add(self.do_cache,priority=GLib.PRIORITY_LOW)
    536         GObject.timeout_add(50, self.check_updated_cache)
    537 
     159        check_args('controller',**kwargs)
     160        self.ctl=kwargs['controller']
     161        #
     162        # Thread pool initialization
     163        #
    538164        self._thread = [None] * 10
    539165        # self.get_used_thread = lambda : self._thread.index(list(filter(None.__ne__,self._thread))[0])
    540166        self.get_thread = lambda: self._thread.index(None)
    541 
     167        #
     168        # N4d initialization
     169        #
    542170        self.n4d = x.ServerProxy("https://server:9779", verbose=False, use_datetime=True,
    543171                             context=ssl._create_unverified_context())
    544 
    545         return
    546 
     172        self.n4d_sepaphore = threading.BoundedSemaphore(value=1)
     173
     174    #
     175    # Method to create threaded job, with or without callback
     176    #
     177    #@trace
    547178    def do_call(self, func, args, callback, nthread=None, prio=GLib.PRIORITY_HIGH_IDLE,block=False):
     179        # Local thread number
    548180        thread = None
     181
     182        # If we are checking finalization of already created thread
    549183        if nthread == None:
     184            # Is a new call
    550185            try:
    551186                try:
     187                    # Get a free thread pool space
    552188                    thread = self.get_thread()
     189                    namethread='thread_num_{}'.format(thread)
     190                    dbg('Launch {} with {}'.format(namethread,func.__name__))
    553191                except:
    554192                    raise Exception('No more threads available')
     193
     194                # Create thread on pool
    555195                if  args!= None:
    556                     self._thread[thread] = threading.Thread(target=func, args=(args,))
     196                    # Thread needs args
     197                    self._thread[thread] = threading.Thread(target=func, args=(args,),name=namethread)
    557198                else:
    558                     self._thread[thread] = threading.Thread(target=func)
    559 
    560 
     199                    # Thread without args
     200                    self._thread[thread] = threading.Thread(target=func,name=namethread)
     201
     202                # Need a threaded blocking call?
    561203                if block:
     204                    # Start blocking thread
    562205                    self._thread[thread].start()
    563206                    self._thread[thread].join()
     207                    # Free thread
    564208                    self._thread[thread] = None
     209
     210                    # If has callback, call passing result or return the result of thread directly
    565211                    if callback == None:
    566212                        return args['result']
     
    568214                        return callback(**args) and False
    569215                else:
     216                    # Start normal thread out of gtk loop (non-blocking)
    570217                    GObject.idle_add(self._thread[thread].start, priority=prio)
     218                    # Program next check thread finalization
    571219                    GObject.timeout_add(50, self.do_call, func, args, callback, thread, prio,block)
     220
     221                # Finalize main call, no need to re-initialize timeouted checking
    572222                return False
    573 
    574223            except:
     224                # Something goes wrong :-(
    575225                raise Exception('Error calling threads')
    576226        else:
     227            # No new call, check if its finalized
     228
     229            # we use the old thread in pool, already started
    577230            thread = nthread
     231
     232            # Check if the job is done
    578233            if self._thread[thread].isAlive() or not self._thread[thread]._started._flag:
     234                # If not done, initialize timeout checking
    579235                return True
    580236            else:
     237                # Job is done
     238                # Free the thread pool space
    581239                self._thread[thread] = None
    582240                try:
     241                    # If has callback, call passing result or return the result of thread directly
    583242                    if callback != None:
     243                        dbg('Callback from thread_num_{}: running function \'{}\''.format(nthread,callback.__name__))
    584244                        return callback(**args) and False
    585245                    else:
     246                        # No direct return value is possible due to non blocking call, result only can be catched by asynchronous function
     247                        # Finalize main call, no need to re-initialize timeouted checking
    586248                        return False
    587249                except Exception as e:
     250                    # Something goes wrong :-(
    588251                    raise e
     252
     253        # In normal conditions no need to reach this point
     254        # Finalize main call, no need to re-initialize timeouted checking
    589255        return False
    590256
    591     def visible_func(self,model,it,data=None,*args,**kwargs):
    592         value_name = model.get_value(it,3)
    593         value_type = model.get_value(it,5)
    594         filter=self.model_used
    595         if value_type != filter:
    596             return False
    597         if self.search == '':
    598             return True
    599         elif self.search.lower() in value_name.lower():
    600             return True
    601         return False
    602 
    603     def refilter(self,*args,**kwargs):
    604         self.filter.refilter()
    605 
    606     def do_cache(self,*args,**kwargs):
    607         self.thread_cache = threading.Thread(target=self.update_cache).start()
    608 
    609     def check_updated_cache(self):
    610         if self.model_used != None:
    611             if self.model_used == 'local':
    612                     if not self.window.is_visible() and self.loaded_cache_local:
    613                         self.load.hide()
    614                         self.btn_goto.set_label('Ver apps sistema')
    615                         self.searchentry.grab_focus()
    616                         self.window.show_all()
    617 
    618             elif self.model_used == 'system':
    619                     if not self.window.is_visible() and self.loaded_cache_system:
    620                         self.load.hide()
    621                         self.btn_goto.set_label('Ver apps plank')
    622                         self.searchentry.grab_focus()
    623                         self.window.show_all()
    624 
    625         if self.updated_cache_system:
    626             if not self.loaded_cache_system:
    627                 self.add_all_elements(self.system_icon_cache,'system')
    628             if self.preselected_apps:
    629                 for a in self.preselected_apps:
    630                     for x in [ x for x in self.store if x[5] == 'system']:
    631                         if a[0] == x[3]:
    632                             self.selected.append(x[0])
    633                             self.preselected_apps.remove(a)
    634                             x[1] = True
    635                             break
    636         if self.updated_cache_local:
    637             if not self.loaded_cache_local:
    638                 self.add_all_elements(self.local_icon_cache,'local')
    639             if self.preselected_apps:
    640                 for a in self.preselected_apps:
    641                     for x in [ x for x in self.store if x[5] == 'local']:
    642                         if a[0] == x[3]:
    643                             self.selected.append(x[0])
    644                             self.preselected_apps.remove(a)
    645                             x[1] = True
    646                             break
    647 
    648         self.progressbar.set_fraction(self.progress_level)
    649         self.progressbar.set_text('{}%   {}'.format(int(self.progress_level * 100), self.progress_text))
    650         self.progressbar2.set_fraction(self.progress_level)
    651         self.progressbar2.set_text('{}%   {}'.format(int(self.progress_level * 100), self.progress_text))
    652 
    653         self.update_status_bar()
    654 
    655         if self.loaded_cache_local == 'loaded' and self.loaded_cache_system == 'loaded' and not self.preselected_apps:
    656             return False
    657         else:
    658             return True
    659 
    660     def update_status_bar(self,*args,**kwargs):
    661         self.statusbar.remove_all(self.statusbar_context)
    662         self.statusbar.push(self.statusbar_context,'{} current elements selected'.format(len(self.selected)))
    663         self.statusbar2.remove_all(self.statusbar_context)
    664         self.statusbar2.push(self.statusbar_context, '{} current elements selected'.format(len(self.selected)))
    665 
    666     def update_cache(self,*args,**kwargs):
    667         self.progress_text="Building cache..."
    668         if self.files_cache == None:
    669 
    670             self.files_cache = glob.glob('/usr/share/icons/**/*.png', recursive=True)
    671 
    672             self.local_icon_cache = self.search_local_plank()
    673             self.updated_cache_local=True
    674             self.loaded_cache_local=False
    675             self.system_icon_cache=self.search_in_applications()
    676             self.updated_cache_system=True
    677             self.loaded_cache_system =False
    678             self.progress_text="Ready"
    679             self.updated_cache=True
    680 
     257
     258
     259    ##############################
     260    #
     261    # Search plank elements into user home
     262    #
     263    ##############################
    681264    def search_local_plank(self,*args,**kwargs):
     265        check_args('files_cache',**kwargs)
     266        files_cache=kwargs['files_cache']
     267
    682268        list_apps=[]
    683         self.progress_text = "Searching & adding apps..."
    684269        theme='Vibrancy-Colors'
    685270
     
    695280                name='Show desktop'
    696281                if complete_filename:
    697                     list_apps.append((complete_filename,basename,basename,filename))
     282                    list_apps.append((complete_filename,basename,basename,filename,name))
    698283            elif basename == 'matecc':
    699284                icon_file='preferences-desktop.png'
     
    701286                name='Control center'
    702287                if complete_filename:
    703                     list_apps.append((complete_filename,basename,basename,filename))
     288                    list_apps.append((complete_filename,basename,basename,filename,name))
    704289            else:
    705290                complete_filename=[filename for filename in glob.iglob('/usr/share/applications/'+basename+'.desktop', recursive=True)][0]
    706                 result=self.scan_desktop_file(complete_filename)
     291                result=self.scan_desktop_file(filename=complete_filename,files_cache=files_cache)
    707292                if result:
    708293                    list_apps.append(result)
     
    710295        return list_apps
    711296
    712     def add_element(self,id,icon,name,path,type,*args,**kwargs):
    713         try:
    714             pix = GdkPixbuf.Pixbuf.new_from_file_at_size(icon,24,24)
    715         except:
    716             print('Error generating pixbuf {}'.format(icon))
    717             pix = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,False,8,24,24)
    718         self.store.append([id, False, pix, name, path, type])
    719 
    720 
    721     def add_all_elements(self,list,type,*args,**kwargs):
    722         if type=='local':
    723             self.loaded_cache_local = True
    724             id=1
    725         else:
    726             self.loaded_cache_system = True
    727             id=1000
    728         for (icon,name,app,path) in list:
    729             self.add_element(id,icon,app,path,type)
    730             id += 1
    731         if type=='system':
    732             self.loaded_cache_system='loaded'
    733         if type=='local':
    734             self.loaded_cache_local='loaded'
    735 
    736     def search_icon_file(self,nameicon,*args,**kwargs):
     297    ##############################
     298    #
     299    # Search location of icon appname
     300    #
     301    ##############################
     302    def search_icon_file(self,*args,**kwargs):
     303        check_args('files_cache','icon',**kwargs)
     304        files_cache=kwargs['files_cache']
     305        nameicon=kwargs['icon']
     306
    737307        try:
    738308            file_names=''
    739309            txt='(.*'+nameicon+'.png)'
    740310            regexp=re.compile(txt)
    741             for filename in self.files_cache:
     311            for filename in files_cache:
    742312                m=regexp.match(filename)
    743313                if m:
     
    745315                    break
    746316        except Exception as e:
    747             return None
     317            raise(e)
    748318        if len(file_names) == 0:
    749             return None
     319            raise Exception('Icon file {} not found'.format(nameicon))
    750320        return file_names
    751321
    752     def scan_desktop_file(self,filename,*args,**kwargs):
     322    ##############################
     323    #
     324    # Parse & extract info from desktop file
     325    #
     326    ##############################
     327    def scan_desktop_file(self,*args,**kwargs):
     328        check_args('files_cache','filename',**kwargs)
     329        filename=kwargs['filename']
     330        files_cache=kwargs['files_cache']
     331
    753332        regexp1 = re.compile('Icon=(.*)',re.IGNORECASE)
    754333        regexp2 = re.compile('Name=(.*)', re.IGNORECASE)
     334        regexp3 = re.compile('Comment=(.*)',re.IGNORECASE)
    755335        try:
    756336            with open(filename, 'r') as f:
     
    758338                icon = None
    759339                appname = None
     340                description = None
    760341                for line in lines:
    761342                    m1 = regexp1.match(line)
    762343                    m2 = regexp2.match(line)
     344                    m3 = regexp3.match(line)
    763345                    if m1 and icon == None:
    764346                        icon = m1.group(1)
    765347                    if m2 and appname == None:
    766348                        appname = m2.group(1)
    767 
    768                 if icon and appname:
     349                    if m3 and m3.group(1):
     350                        description=m3.group(1)
     351
     352                #
     353                # Commented due to allow desktops with no icon or description
     354                #
     355                #if icon and appname and description:
     356                if appname:
    769357                    desktop_basename = filename.split('/')[-1].split('.')[0]
    770                     if 'png' in icon.split('.'):
    771                         icon_path = icon
     358                    if icon != None:
     359                        if 'png' in icon.split('.'):
     360                            icon_path = icon
     361                        else:
     362                            try:
     363                                icon_path = self.search_icon_file(icon=icon,files_cache=files_cache)
     364                            except Exception as e:
     365                                # Allow tolerance if no icon is found
     366                                icon_path = None
     367                        if desktop_basename:
     368                            return (icon_path, appname, desktop_basename,filename,description)
    772369                    else:
    773                         icon_path = self.search_icon_file(icon)
    774                     if icon_path and appname and desktop_basename:
    775                         return (icon_path, appname, desktop_basename,filename)
    776         except:
    777             return None
    778 
     370                        # icon or description are None
     371                        return (icon,appname,desktop_basename,filename,description)
     372                else:
     373                #    raise Exception('Can\'t get icon/appname/description from desktop file \'{}\''.format(filename))
     374                     raise Exception('Can\'t get appname from desktop file \'{}\''.format(filename))
     375        except Exception as e:
     376            raise(e)
     377
     378    ##############################
     379    #
     380    # Search system applications
     381    #
     382    ##############################
    779383    def search_in_applications(self,*args,**kwargs):
    780         self.progress_text="Searching & adding apps..."
     384        check_args('cache_info','files_cache',**kwargs)
     385        info=kwargs['cache_info']
     386        files_cache=kwargs['files_cache']
     387
    781388        try:
    782389            file_names = [filename for filename in glob.iglob('/usr/share/applications/*.desktop', recursive=True)]
    783390        except:
    784391            return None
     392
    785393        outlist=[]
    786394        procesed_items=0
    787395        total_items=len(file_names)
    788         self.progress_text = "Scanning apps..."
     396
     397        info['msg'] = _("Scanning system apps...")
     398        # FILTER SOME ELEMENTS WITH SAME NAME
     399        not_repeated_list = {}
     400        filter_column = 1
     401        column4change_if_seems_the_same = 2
    789402        for desktop in file_names:
    790             result=self.scan_desktop_file(desktop)
    791             if result:
    792                 outlist.append(result)
     403            try:
     404                result=self.scan_desktop_file(filename=desktop,files_cache=files_cache)
     405                if result:
     406                    if result[filter_column] not in not_repeated_list:
     407                        not_repeated_list[result[filter_column]]=result
     408                    else:
     409                        # if seems the same element change 'name' with 'basename'
     410                        # First change old element found
     411                        temp1=list(not_repeated_list[result[filter_column]])
     412                        del not_repeated_list[result[filter_column]]
     413                        temp1[filter_column]=temp1[column4change_if_seems_the_same]
     414                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp1)
     415                        # Second insert new element found
     416                        temp2=list(result)
     417                        temp2[filter_column]=temp2[column4change_if_seems_the_same]
     418                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp2)
     419            except Exception as e:
     420                dbg(e)
     421                pass
     422
    793423            procesed_items += 1
    794             self.progress_level=procesed_items/total_items
    795 
     424            info['level']=procesed_items/total_items
     425
     426        for x in not_repeated_list:
     427            outlist.append(not_repeated_list[x])
    796428        return outlist
    797429
    798     def get_profiles_stored(self,*args,**kwargs):
    799         if 'result' in kwargs:
    800             self.profiles = []
    801             for p in kwargs['result']:
    802                 self.profiles.append(p)
     430    ##############################
     431    #
     432    # Clean status_bar_with_timeout
     433    #
     434    ##############################
     435    def clean_status_bar(self,*args,**kwargs):
     436        def sleep_and_clean(x):
     437            time.sleep(x.get('sleep'))
     438            bar_info = {str(x.get('num_bar')): ''}
     439            self.ctl.window.set_txt_status_bars(bar_info=bar_info)
     440
     441        self.do_call(sleep_and_clean,{'sleep':3,'num_bar':args[0]},None)
     442
     443        return
     444
     445    ##############################
     446    #
     447    # N4D validation call
     448    #
     449    ##############################
     450    #@trace
     451    def n4d_validate(self,*args,**kwargs):
     452        check_args('user','pwd',**kwargs)
     453        d = {'args': (kwargs['user'], kwargs['pwd']),'action':'validation_end'}
     454        #login_func = lambda x: x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
     455        #@trace
     456        def login_func(x):
     457            try:
     458                self.n4d_sepaphore.acquire()
     459                x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
     460                self.n4d_sepaphore.release()
     461            except Exception as e:
     462                raise Exception(e)
     463        return self.do_call(login_func, d, self.ctl.process)
     464
     465    ##############################
     466    #
     467    # N4D call to get ldap groups
     468    #
     469    ##############################
     470    #@trace
     471    def n4d_get_system_groups(self,*args,**kwargs):
     472        check_args('user','pwd',**kwargs)
     473        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_groups'}
     474        #get_groups = lambda x: x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
     475        #@trace
     476        def get_groups(x):
     477            try:
     478                self.n4d_sepaphore.acquire()
     479                x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
     480                self.n4d_sepaphore.release()
     481            except Exception as e:
     482                raise Exception(e)
     483        return self.do_call(get_groups, d, self.ctl.process)
     484
     485    ##############################
     486    #
     487    # N4D call to get currently stored profiles
     488    #
     489    ##############################
     490    #@trace
     491    def n4d_get_profiles_stored(self,*args,**kwargs):
     492        check_args('user','pwd',**kwargs)
     493        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_profiles'}
     494        #get_profiles = lambda x: x.update({'result': self.n4d.get_profiles(x.get('args'),'PlankSync')})
     495        #@trace
     496        def get_profiles(x):
     497            try:
     498                self.n4d_sepaphore.acquire()
     499                x.update({'result': self.n4d.get_profiles(x.get('args'), 'PlankSync')})
     500                self.n4d_sepaphore.release()
     501            except Exception as e:
     502                raise Exception(e)
     503        return self.do_call(get_profiles, d, self.ctl.process)
     504
     505    ##############################
     506    #
     507    # N4D call to remove profile on filesystem
     508    #
     509    ##############################
     510    #@trace
     511    def n4d_remove_profile(self,*args,**kwargs):
     512        check_args('user','pwd','name',**kwargs)
     513        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'],'action':'remove_profile_done'}
     514        #remove_profile = lambda x: x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
     515        #@trace
     516        def remove_profile(x):
     517            try:
     518                self.n4d_sepaphore.acquire()
     519                x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
     520                self.n4d_sepaphore.release()
     521            except Exception as e:
     522                raise Exception(e)
     523        return self.do_call(remove_profile, d, self.ctl.process)
     524
     525    ##############################
     526    #
     527    # N4D call to get details about profile
     528    #
     529    ##############################
     530    #@trace
     531    def n4d_read_profile(self,*args,**kwargs):
     532        #
     533        # Option to set new parameters
     534        #
     535        d = {}
     536        try:
     537            check_args('extraparam',**kwargs)
     538            for nameparam in kwargs['extraparam']:
     539                d[nameparam]=kwargs['extraparam'][nameparam]
     540        except:
     541            pass
     542        check_args('user', 'pwd', 'name', **kwargs)
     543        d['args'] = (kwargs['user'],kwargs['pwd'])
     544        d['name'] = kwargs['name']
     545        d['action'] = 'load_profile_into_liststore'
     546        #get_profile = lambda x: x.update({'result': self.window.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
     547        #@trace
     548        def get_profile(x):
     549            try:
     550                self.n4d_sepaphore.acquire()
     551                x.update({'result': self.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
     552                self.n4d_sepaphore.release()
     553            except Exception as e:
     554                raise Exception(e)
     555        #
     556        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
     557        #
     558        try:
     559            check_args('noprocess',**kwargs)
     560            new_callback_func=kwargs['noprocess']
     561            callback=new_callback_func
     562        except:
     563            callback=self.ctl.process
     564
     565        return self.do_call(get_profile, d, callback)
     566
     567    ##############################
     568    #
     569    # N4D call to rename profile
     570    #
     571    ##############################
     572    def n4d_rename_profile(self,*args,**kwargs):
     573        check_args('user','pwd','old','new',**kwargs)
     574        d = {'args': (kwargs['user'],kwargs['pwd']), 'oldname': kwargs['old'], 'newname': kwargs['new'],'action':'profile_is_renamed'}
     575        #rename_profile = lambda x: x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
     576        def rename_profile(x):
     577            try:
     578                self.n4d_sepaphore.acquire()
     579                x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
     580                self.n4d_sepaphore.release()
     581            except Exception as e:
     582                raise Exception(e)
     583        return self.do_call(rename_profile, d, self.ctl.process)
     584
     585    ##############################
     586    #
     587    # N4D call to create or update profile
     588    #
     589    ##############################
     590    def n4d_update_profile(self,*args,**kwargs):
     591        check_args('user','pwd','name','grouplist','applist',**kwargs)
     592        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'group': kwargs['grouplist'], 'applist': kwargs['applist'], 'action':'profile_is_updated'}
     593        try:
     594            check_args('status_bar',**kwargs)
     595            d.update({'status_bar':kwargs['status_bar']})
     596        except:
     597            pass
     598        #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))})
     599        def update_profile(x):
     600            try:
     601                self.n4d_sepaphore.acquire()
     602                x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),
     603                                                            x.get('applist'))})
     604                self.n4d_sepaphore.release()
     605            except Exception as e:
     606                raise Exception(e)
     607        #
     608        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
     609        #
     610        try:
     611            check_args('noprocess', **kwargs)
     612            new_callback_func = kwargs['noprocess']
     613            callback = new_callback_func
     614        except:
     615            callback = self.ctl.process
     616        return self.do_call(update_profile, d, callback)
     617
     618##############################
     619#
     620# Main visualization class
     621#
     622##############################
     623class MainWindow:
     624
     625    def __init__(self,*args,**kwargs):
     626        check_args('controller','handler',**kwargs)
     627        self.ctl = kwargs['controller']
     628        self.handler = kwargs['handler']
     629
     630        #
     631        # Show main window
     632        #
     633
     634        self.obj = self.ctl.getobj('window_main')
     635
     636        #
     637        # build stack components for the window
     638        #
     639        self.boxes_main = ['box_login','box_loading','box_main_btn','box_profiles','box_dialog','box_pselector','box_group_edit','box_warning']
     640        self.boxes_bottom = ['box_lvl','box_status']
     641        self.stack_main = {'obj': self.build_stack(boxes=self.boxes_main),'stack':[]}
     642        self.stack_bottom = { 'obj': self.build_stack(boxes=self.boxes_bottom), 'stack':[]}
     643
     644        #
     645        # get place to put stack
     646        #
     647        self.box_main_center=self.ctl.getobj('box_main_center')
     648        self.box_main_bottom=self.ctl.getobj('box_main_bottom')
     649
     650        #
     651        # Add stacks
     652        #
     653        self.box_main_center.pack_start(self.stack_main['obj'],True,True,0)
     654        self.box_main_bottom.pack_start(self.stack_bottom['obj'],True,True,0)
     655
     656        #
     657        # Store references to programatically created objects
     658        #   boxes with labels and combos storing relations
     659        #
     660        self.group_profile_boxes = {}
     661
     662        # Show window
     663        self.obj.show_all()
     664
     665    ##############################
     666    #
     667    # Builds dynamically boxes with an entry and comboboxes because
     668    #   treeview with comboboxrenderer isn't a valid option
     669    #
     670    ##############################
     671    def build_group_profile_window(self,*args,**kwargs):
     672        # Get params from controller
     673        check_args('profiles','groups',**kwargs)
     674        profiles=kwargs['profiles']
     675        groups=kwargs['groups']
     676
     677        # Get glade main object from builder
     678        container=self.ctl.getobj('box_container_grp_profiles')
     679
     680        if len(self.group_profile_boxes) == 0:
     681            #
     682            # if it's the first time called build the structure
     683            # if it isn't the first time the objects are already stored
     684            #
     685
     686            # Build ListStore 4 showing into ComboBox
     687            lst = Gtk.ListStore(GObject.TYPE_INT,GObject.TYPE_STRING)
     688            # Add fake empty first element
     689            lst.append([0,'[None]'])
     690            # Id entry into combobox model
     691            i=1
     692            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
     693            grp_profile={}
     694            # Complete ListStore & do the mapping
     695            for profile in profiles:
     696                pname=profile[0]
     697                pgrp=profile[1]
     698                lst.append([i,pname])
     699                if pgrp != '':
     700                    for g in pgrp.split(','):
     701                        grp_profile[g] = i
     702                i += 1
     703
     704            # Create Boxes (rows) per group
     705            for gname in groups:
     706                # A Box
     707                box=Gtk.Box(Gtk.Orientation.HORIZONTAL,10)
     708                box.set_property('homogeneous',True)
     709                box.set_property('halign',Gtk.Align.FILL)
     710                # With Label
     711                lbl=Gtk.Label(gname)
     712                lbl.set_property('halign',Gtk.Align.CENTER)
     713                # And ComboBox
     714                combo=Gtk.ComboBox.new_with_model_and_entry(lst)
     715                combo.set_property('halign',Gtk.Align.START)
     716                combo.set_property('width-request',200)
     717                combo.set_property('entry-text-column',1)
     718                combo.set_property('id-column', 0)
     719                # Set active element if already associated
     720                if gname in grp_profile:
     721                    combo.set_property('active', grp_profile[gname])
     722                else:
     723                    combo.set_property('active', 0)
     724                # Connect signals
     725                combo.connect("changed", self.handler.combo_changed,lbl)
     726                # Pack everything
     727                box.pack_start(lbl,True,True,0)
     728                box.pack_start(combo,True,True,0)
     729                # Save references to avoid redo the objects
     730                self.group_profile_boxes.update({gname:[lbl,combo]})
     731                container.pack_start(box,False,False,0)
    803732        else:
    804             d = {'args': (self.user,self.pwd)}
    805             get_profiles = lambda x: x.update({'result': self.n4d.get_profiles(x.get('args'),'PlankSync')})
    806             self.do_call(get_profiles, d, self.get_profiles_stored,block=True)
    807 
    808         return self.profiles
    809 
    810     def get_system_groups(self,*args,**kwargs):
    811         if 'result' in kwargs:
    812             grps = kwargs['result']
    813             self.groups = []
    814             for g in [x['cn'][0] for x in grps]:
    815                 self.groups.append(g)
     733            #
     734            # Objects are already created, they only need to be updated
     735            #
     736
     737            # Id entry into combobox model
     738            i = 1
     739            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
     740            grp_profile = {}
     741            # Do the mapping
     742            for profile in profiles:
     743                pgrp=profile[1]
     744                if pgrp != '':
     745                    for g in pgrp.split(','):
     746                        grp_profile[g] = i
     747                i += 1
     748            for group in groups:
     749                if group in grp_profile:
     750                    self.group_profile_boxes[group][1].set_property('active',grp_profile[group])
     751                else:
     752                    self.group_profile_boxes[group][1].set_property('active', 0)
     753
     754
     755        # Show all widgets
     756        container.show_all()
     757
     758    ##############################
     759    #
     760    # Build stack components with boxes from glade
     761    #
     762    ##############################
     763    def build_stack(self,*args,**kwargs):
     764        stack=Gtk.Stack()
     765        for box_name in kwargs['boxes']:
     766            box=self.ctl.getobj(box_name)
     767            stack.add_named(box,box_name)
     768        stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
     769        stack.set_transition_duration(400)
     770        return stack
     771
     772    ##############################
     773    #
     774    # Change visualization elements from stacks
     775    #
     776    ##############################
     777    def change_stack(self,*args,**kwargs):
     778        # Get the stack and properties
     779        if 'stack' not in kwargs:
     780            stack=self.stack_main['obj']
     781            history=self.stack_main['stack']
     782            lstack=self.boxes_main
     783            visible_name = stack.get_visible_child_name()
    816784        else:
    817             d = {'args': (self.user,self.pwd)}
    818             get_groups = lambda x: x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
    819             self.do_call(get_groups, d, self.get_system_groups,block=True)
    820 
    821         return self.groups
    822 
    823     def rename_profile(self,old,new,*args,**kwargs):
    824         d = {'args': (self.user, self.pwd),'oldname':old,'newname':new}
    825         rename_profile = lambda x: x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync',x.get('oldname'),x.get('newname'))})
    826         return self.do_call(rename_profile, d, None, block=True)
    827 
    828     def remove_profile(self,name,*args,**kwargs):
    829         d = {'args': (self.user, self.pwd), 'name': name}
    830         remove_profile = lambda x: x.update(
    831             {'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
    832         return self.do_call(remove_profile, d, None, block=True)
    833 
    834     def update_profile(self, name,grouplist,applist,*args, **kwargs):
    835         d = {'args': (self.user, self.pwd), 'name': name,'group':grouplist,'applist':applist}
    836         update_profile = lambda x: x.update(
    837             {'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'),x.get('group'),x.get('applist'))})
    838         return self.do_call(update_profile, d, None, block=True)
    839 
    840 
    841 
     785            if type(kwargs['stack']) == type(str()):
     786                if kwargs['stack'] == 'main':
     787                    stack=self.stack_main['obj']
     788                    history = self.stack_main['stack']
     789                    lstack = self.boxes_main
     790                    visible_name = stack.get_visible_child_name()
     791                else:
     792                    stack=self.stack_bottom['obj']
     793                    history = self.stack_bottom['stack']
     794                    lstack = self.boxes_bottom
     795                    visible_name = stack.get_visible_child_name()
     796            else:
     797                stack=kwargs['stack']
     798                visible_name = stack.get_visible_child_name()
     799                if visible_name in self.boxes_main:
     800                    lstack=self.boxes_main
     801                else:
     802                    lstack=self.boxes_bottom
     803
     804        lindex=lstack.index(visible_name)
     805        next=lindex
     806        for to in args:
     807            try:
     808                if to == 'next':
     809                    next += (lindex + 1) % len(lstack)
     810                elif to == 'prev':
     811                    next += (lindex - 1) % len(lstack)
     812                elif to == 'back':
     813                    next = lstack.index(history[1])
     814                elif type(to) == type(int()):
     815                    next += (lindex + to) % len(lstack)
     816                else:
     817                    if to in lstack:
     818                        next = lstack.index(to)
     819            except:
     820                pass
     821
     822        stack.set_visible_child_name(lstack[next])
     823        self.ctl.last_window = lstack[next]
     824
     825        if to != 'back':
     826            history.insert(0,lstack[next])
     827        else:
     828            del history[0]
     829
     830        if len(history) > 10:
     831            del history[10]
     832
     833
     834    ##############################
     835    #
     836    # Change button label
     837    #
     838    ##############################
     839    def change_button_text(self,*args,**kwargs):
     840        if not check_args('button','text',**kwargs):
     841            return
     842        btn=self.ctl.getobj(kwargs['button'])
     843        btn.set_label(kwargs['text'])
     844
     845    ##############################
     846    #
     847    # Update level bar & label
     848    #
     849    ##############################
     850    #@trace
     851    def update_status_bar(self,*args,**kwargs):
     852
     853        bar=self.ctl.getobj('lvlbar')
     854        label=self.ctl.getobj('label_lvlbar')
     855        info=args[0]
     856        done=None
     857        while not done:
     858            time.sleep(0.1)
     859            bar.set_value(info['level'])
     860            label.set_text(info['msg'])
     861            done=info['done']
     862        return
     863
     864    ##############################
     865    #
     866    # Update with text any status bar
     867    #
     868    ##############################
     869    def set_txt_status_bars(self,*args,**kwargs):
     870        bars=[]
     871        contexts=[]
     872        for i in range(3):
     873            bars.insert(i,self.ctl.getobj('stbar'+str(i+1)))
     874            contexts.insert(i,bars[i].get_context_id(str(i)))
     875        if 'clean' in kwargs:
     876            for nbar in range(3):
     877                bars[nbar].remove_all(contexts[nbar])
     878            return
     879        for i in range(3):
     880            if len(args) > i:
     881                bars[i].remove_all(contexts[i])
     882                bars[i].push(contexts[i],args[i])
     883        if 'bar_info' in kwargs:
     884            bar_info = kwargs['bar_info']
     885            for num in bar_info:
     886                if num in ['0','1','2']:
     887                    bars[int(num)].remove_all(contexts[int(num)])
     888                    bars[int(num)].push(contexts[int(num)],bar_info[num])
     889        return
     890
     891    ##############################
     892    #
     893    # Get values from entries or labels
     894    #
     895    ##############################
     896    def get_entry(self, *args, **kwargs):
     897        lout = []
     898        for name in args:
     899            entry = self.ctl.getobj(name)
     900            lout.append(entry.get_text())
     901        return lout
     902
     903    ##############################
     904    #
     905    # Set values into entries or labels
     906    #
     907    ##############################
     908    def set_entry(self, *args, **kwargs):
     909        for entry in args:
     910            for name_entry in entry:
     911                value = entry[name_entry]
     912                self.ctl.getobj(name_entry).set_text(value)
     913        return
     914
     915##############################
     916#
     917# Event handler main class
     918#
     919##############################
     920class Handler():
     921    def __init__(self,*args,**kwargs):
     922        check_args('controller',**kwargs)
     923        self.ctl=kwargs['controller']
     924
     925    def get_selected_rowid(self,*args,**kwargs):
     926        check_args('selected_obj','type',**kwargs)
     927        #
     928        # Get the selected element, returns elements id or none
     929        #
     930        treeview_selection = kwargs['selected_obj']
     931        typearg = kwargs['type']
     932        model, path = treeview_selection.get_selected_rows()
     933        # If nothing is selected abort actions
     934        if (len(path) < 1):
     935            return None
     936        selected = None
     937        if typearg == 'apps':
     938            selected = model[path][APPCOL.ID]
     939        if typearg == 'profiles':
     940            selected = model[path][PROFCOL.ID]
     941        return selected
     942
     943    def destroy(self, *args, **kwargs):
     944        self.ctl.kill_all_pending_operations()
     945        Gtk.main_quit(*args)
     946
     947    def delete_event(self,*args, **kwargs):
     948        for w in args:
     949            w.hide()
     950        return True
     951
     952    def validate_clicked(self,*args,**kwargs):
     953        self.ctl.process(action='validation_init')
     954
     955    def go_to_new_profile(self,*args,**kwargs):
     956        self.ctl.process(action='goto',to='new_profile')
     957
     958    def go_to_edit_profile(self,*args,**kwargs):
     959        self.ctl.process(action='goto', to='edit_profile')
     960
     961    def go_to_group_mapping(self,*args,**kwargs):
     962        self.ctl.process(action='goto', to='group_mapping')
     963
     964    def close_window_and_go_back(self,*args,**kwargs):
     965        self.ctl.process(action='goto', to='back')
     966
     967    def entry_filter_changed(self,*args,**kwargs):
     968        self.ctl.process(action='refilter_elements')
     969
     970    def swap(self,*args,**kwargs):
     971        id=self.get_selected_rowid(selected_obj=args[0],type='apps')
     972        self.ctl.process(action='swap',id=id)
     973
     974    def swap_local_elements(self,*args,**kwargs):
     975        self.ctl.process(action='swap_locals')
     976
     977    def go_to_ask_remove_profile(self,*args,**kwargs):
     978        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
     979        if id != None:
     980            self.ctl.process(action='goto', to='ask_remove',id=id)
     981
     982    def load_selected_profile(self,*args,**kwargs):
     983        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
     984        if id != None:
     985            self.ctl.process(action='goto',to='editing_profile',id=id)
     986
     987    def confirm_yes(self,*args,**kwargs):
     988        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
     989        if id != None:
     990            self.ctl.process(action='goto',to='remove_confirmed',id=id)
     991
     992    def profile_name_changed(self,*args,**kwargs):
     993        self.ctl.profile_name_changed = True
     994
     995    def go_to_main_window(self,*args,**kwargs):
     996        self.ctl.process(action='goto',to='main_window')
     997
     998    def combo_changed(self,*args,**kwargs):
     999        #
     1000        # Omit handler when view is building to avoid fake checkings & processing
     1001        #
     1002        if not self.ctl.building_view:
     1003            combo = args[0]
     1004            label = args[1]
     1005            textlabel = label.get_text()
     1006            iter = combo.get_active_iter()
     1007            model = combo.get_model()
     1008            selected = model[iter][1]
     1009            self.ctl.process(action='change_relations',group=textlabel,profile=selected)
     1010
     1011##############################
     1012#
     1013# Liststore enumerations
     1014#
     1015##############################
     1016class APPCOL():
     1017    VISIBLE = 0
     1018    TYPE = 1
     1019    ID = 2
     1020    ICON = 3
     1021    NAME = 4
     1022    DESCRIPTION = 5
     1023    SELECTED = 6
     1024    BASENAME = 7
     1025class PROFCOL():
     1026    VISIBLE = 0
     1027    ID = 1
     1028    NAME = 2
     1029    SELECTED = 3
     1030##############################
     1031#
     1032# Data model main class
     1033#
     1034##############################
     1035class Model:
     1036    def __init__(self, *args, **kwargs):
     1037        check_args('controller',**kwargs)
     1038        self.ctl=kwargs['controller']
     1039
     1040        self.user=None
     1041        self.pwd=None
     1042        self.is_validated=False
     1043        #self.groups=None
     1044        #self.profiles=None
     1045
     1046        self.profile_list=[]
     1047        self.group_list=[]
     1048
     1049        self.cache_lists={}
     1050        self.cache_info={'level':0.0,'msg':'','done':False}
     1051
     1052        self.store_elements = self.ctl.getobj('liststore_elements')
     1053        self.store_profiles = self.ctl.getobj('liststore_profiles')
     1054        self.n_selected = 0
     1055        self.current_relations = {}
     1056
     1057        self.init_sorted_filtering()
     1058        pass
     1059
     1060    def build_liststore(self,*args,**kwargs):
     1061        #
     1062        # Build liststore
     1063        #
     1064        check_args('type',**kwargs)
     1065        typearg=kwargs['type']
     1066        self.clear_liststore(type=typearg)
     1067        if typearg == 'apps':
     1068            for t in ['local','system']:
     1069                self.add_to_liststore(list=self.cache_lists[t], type=t)
     1070        if 'profiles' == typearg:
     1071             self.add_to_liststore(list=self.profile_list, type=typearg)
     1072
     1073
     1074    ##############################
     1075    #
     1076    # Initialize liststore filters & sorting
     1077    #
     1078    ##############################
     1079    def init_sorted_filtering(self,*args,**kwargs):
     1080        for objname in ['elements','selected']:
     1081            obj=self.ctl.getobj('sort_'+objname)
     1082            # Init sorting
     1083            sort_column_in_model=APPCOL.NAME
     1084            obj.set_sort_column_id(sort_column_in_model,Gtk.SortType.ASCENDING)
     1085            model_filter=self.ctl.getobj('filter_'+objname)
     1086            # Init filters
     1087            if objname == 'selected':
     1088                model_filter.set_visible_func(self.filter_func_selected)
     1089            else:
     1090                model_filter.set_visible_func(self.filter_func_apps)
     1091
     1092
     1093    ##############################
     1094    #
     1095    # Empty liststores
     1096    #
     1097    ##############################
     1098    def clear_liststore(self,*args,**kwargs):
     1099        check_args('type', **kwargs)
     1100        store=kwargs['type']
     1101        if store == 'apps':
     1102            self.store_elements.clear()
     1103        if 'profiles' == store:
     1104            self.store_profiles.clear()
     1105
     1106    ##############################
     1107    #
     1108    # Set selections into liststore
     1109    #
     1110    ##############################
     1111    def set_liststore_selections(self,*args,**kwargs):
     1112        check_args('list','type', **kwargs)
     1113        store = kwargs['type']
     1114        app_list = kwargs['list']
     1115        app_list = [name.lower() for name in app_list]
     1116        current_store = None
     1117        id_element_selection = None
     1118        id_element_name = None
     1119
     1120        if store == 'apps':
     1121            current_store = self.store_elements
     1122            id_element_selection = APPCOL.SELECTED
     1123            id_element_name = APPCOL.BASENAME
     1124        if 'profiles' == store:
     1125            current_store = self.store_profiles
     1126            id_element_selection = PROFCOL.SELECTED
     1127            id_element_name = PROFCOL.NAME
     1128        # If name not exist, all selections are cleared
     1129        for x in current_store:
     1130            if x[id_element_name].lower() in app_list:
     1131                x[id_element_selection] = True
     1132                app_list.remove(x[id_element_name].lower())
     1133            elif x[id_element_selection]:
     1134                x[id_element_selection] = False
     1135            if len(app_list) == 0:
     1136                break
     1137        return
     1138
     1139    ##############################
     1140    #
     1141    # Get current selections from liststore
     1142    #
     1143    ##############################
     1144    def get_liststore_selections(self,*args,**kwargs):
     1145        check_args('type',**kwargs)
     1146        store=kwargs['type']
     1147
     1148        current_store = None
     1149        if store == 'apps':
     1150            current_store = self.store_elements
     1151            id_element_selection = APPCOL.SELECTED
     1152            id_element_target = APPCOL.BASENAME
     1153        if store == 'profiles':
     1154            current_store = self.store_profiles
     1155            id_element_selection = PROFCOL.SELECTED
     1156            id_element_target = PROFCOL.NAME
     1157
     1158        applist = []
     1159        for x in current_store:
     1160            if x[id_element_selection]:
     1161                applist.append(x[id_element_target])
     1162        return applist
     1163
     1164    ##############################
     1165    #
     1166    # Get current assigned group from profile or None
     1167    #
     1168    ##############################
     1169    def get_group_from_profile(self,*args,**kwargs):
     1170        check_args('nameprofile',**kwargs)
     1171        name=kwargs['nameprofile']
     1172        name=name.strip()
     1173
     1174        for p in self.profile_list:
     1175            if p[0].lower() == name.lower():
     1176                return p[1]
     1177        return None
     1178
     1179    ##############################
     1180    #
     1181    # Add list with data into liststore
     1182    #
     1183    ##############################
     1184    def add_to_liststore(self,*args,**kwargs):
     1185        check_args('list','type',**kwargs)
     1186        lapps=kwargs['list']
     1187        typearg=kwargs['type']
     1188        if typearg == 'local' or typearg == 'system':
     1189            if typearg == 'local':
     1190                i = 1
     1191            else:
     1192                i = 1000
     1193            for item in lapps:
     1194                #
     1195                # Item
     1196                # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
     1197                # Liststore:
     1198                # [0]visible(bool), [1]type(string), [2]id(int(1<local<1000<system)), [3]icon(GdkPixbuf), [4]name(string), [5]description(string), [6]selected(bool), [7]desktop_basename(string)
     1199                #
     1200
     1201                # Create GdkPixbuf
     1202                if item[0] == None:
     1203                    # ICON is NONE
     1204                    pix = GdkPixbuf.Pixbuf.new_from_file_at_size('noicon.png', 24, 24)
     1205                else:
     1206                    try:
     1207                        pix = GdkPixbuf.Pixbuf.new_from_file_at_size(item[0], 24, 24)
     1208                    except:
     1209                        dbg('Error generating pixbuf {}'.format(item[0]))
     1210                        pix = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 24, 24)
     1211                if item[4] == None:
     1212                    # DESCRIPTION is NONE
     1213                    desc = 'No description in desktop file'
     1214                else:
     1215                    desc = item[4]
     1216                self.store_elements.append([True,typearg,i,pix,item[1],desc,False,item[2]])
     1217                i += 1
     1218        elif typearg == 'profiles':
     1219            i=0
     1220            for p in self.profile_list:
     1221                self.store_profiles.append([True,i,p[0],False])
     1222                i += 1
     1223
     1224    ##############################
     1225    #
     1226    # Purge duplicated items from cache lists
     1227    #
     1228    ##############################
     1229    def purge_duplicated_cache_items(self,*args,**kwargs):
     1230        l = []
     1231        # Item
     1232        # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
     1233        FILTER_COL=1
     1234        for type in ['system','local']:
     1235            for i in self.cache_lists[type]:
     1236                if not i[FILTER_COL] in l:
     1237                    l.append(i[FILTER_COL])
     1238                else:
     1239                    dbg('Duplicated entry {}'.format(i[FILTER_COL]))
     1240                    self.cache_lists[type].remove(i)
     1241
     1242    ##############################
     1243    #
     1244    # Visible function 4 selected treeview
     1245    #
     1246    ##############################
     1247    def filter_func_selected(self,*args,**kwargs):
     1248        model=args[0]
     1249        iter=args[1]
     1250        is_selected = model.get_value(iter,APPCOL.SELECTED)
     1251        if is_selected:
     1252            return True
     1253        return False
     1254    ##############################
     1255    #
     1256    # Visible function 4 system elements treeview
     1257    #
     1258    ##############################
     1259    def filter_func_apps(self,*args,**kwargs):
     1260        model = args[0]
     1261        iter = args[1]
     1262
     1263        selected = model.get_value(iter, APPCOL.SELECTED)
     1264        if selected:
     1265            return False
     1266
     1267        name = model.get_value(iter,APPCOL.NAME)
     1268        description = model.get_value(iter,APPCOL.DESCRIPTION)
     1269
     1270        entry_text=self.ctl.window.get_entry('entry_filter')
     1271        entry_text=entry_text[0].lower().strip()
     1272        if entry_text == '':
     1273            return True
     1274
     1275        if entry_text in name.lower() or entry_text in description:
     1276            return True
     1277        return False
     1278
     1279    ##############################
     1280    #
     1281    # Trigger refilter on treeview
     1282    #
     1283    ##############################
     1284    def refilter(self,*args,**kwargs):
     1285        check_args('type',**kwargs)
     1286        type_refilter=kwargs['type']
     1287
     1288        model_filter_elements=self.ctl.getobj('filter_elements')
     1289        model_filter_elements.refilter()
     1290        if type_refilter == 'elements':
     1291            return
     1292        model_filter_selected=self.ctl.getobj('filter_selected')
     1293        model_filter_selected.refilter()
     1294        return
     1295
     1296    ##############################
     1297    #
     1298    # Count selected apps
     1299    #
     1300    ##############################
     1301    def count_selected(self,*args,**kwargs):
     1302        i=0
     1303        for a in self.store_elements:
     1304           if a[APPCOL.SELECTED]:
     1305               i+=1
     1306        self.n_selected=i
     1307        return i
     1308
     1309    ##############################
     1310    #
     1311    # Load apps readed from filesystem into liststore before show profile window
     1312    #
     1313    ##############################
     1314    def load_profile_into_liststore(self,*args,**kwargs):
     1315        check_args('apps_selected',**kwargs)
     1316        apps_selected=kwargs['apps_selected']
     1317
     1318        # Clear selections is done when setting selections
     1319        self.set_liststore_selections(list=apps_selected,type='apps')
     1320        return
     1321
     1322    ##############################
     1323    #
     1324    # Check valid profile name
     1325    #
     1326    ##############################
     1327    def check_profile_name(self,*args,**kwargs):
     1328        check_args('name','type',**kwargs)
     1329        name = kwargs['name']
     1330        typearg = kwargs['type']
     1331        name = name.strip()
     1332        name = name.lower()
     1333
     1334        if name == '':
     1335            return False
     1336
     1337        if typearg == 'available':
     1338            ret_pr = True
     1339        else:
     1340            ret_pr = False
     1341
     1342        for p in self.profile_list:
     1343            if typearg == 'available':
     1344                if p[0].lower() == name.lower():
     1345                    ret_pr = False
     1346            elif typearg == 'valid':
     1347                if p[0].lower() == name.lower():
     1348                    ret_pr = True
     1349        return ret_pr
     1350
     1351##############################
     1352#
     1353# Controller class
     1354#
     1355##############################
     1356class Controller:
     1357    def __init__(self,*args,**kwargs):
     1358        self.aborting_all_operations = False
     1359        self.builder=self.load_glade(**kwargs)
     1360
     1361        #
     1362        # Main objects
     1363        #
     1364        self.helper=Helper(controller=self)
     1365        self.model=Model(controller=self)
     1366        self.handler=Handler(controller=self)
     1367        self.window=MainWindow(controller=self,handler=self.handler)
     1368        #
     1369        # Connect signals
     1370        #
     1371        self.builder.connect_signals(self.handler)
     1372
     1373        # Bypass combobox signal handler
     1374        self.building_view = False
     1375
     1376        #
     1377        # Remember what user are doing
     1378        #
     1379        self.last_window = None
     1380        # new_profile or the profile_name what is currently being modified
     1381        self.user_is_editing=None
     1382        # Flags to remember what changed into window
     1383        self.profile_name_changed = None
     1384        self.applist_changed = None
     1385
     1386        #
     1387        # Init actions
     1388        #
     1389        self.reset_memories()
     1390        self.process(action='initialized')
     1391        return
     1392
     1393    ##############################
     1394    #
     1395    # Emergency exit, stops pending threads & silence errors when called destroy from gui
     1396    #    (not catching TERM or KILL signals from terminal)
     1397    #
     1398    ##############################
     1399    def kill_all_pending_operations(self,*args,**kwargs):
     1400        self.aborting_all_operations = True
     1401        for t in self.helper._thread:
     1402            if t != None:
     1403                terminate_thread(t)
     1404    #
     1405    # Reset the status flags
     1406    #
     1407    def reset_memories(self,*args,**kwargs):
     1408        self.last_window = None
     1409        self.user_is_editing = None
     1410        self.profile_name_changed = False
     1411        self.applist_changed = False
     1412    #
     1413    # Load glade file
     1414    #
     1415    def load_glade(self,*args,**kwargs):
     1416        if kwargs['localpath']:
     1417            gladefile = kwargs['localpath']+'/'+"gui_v2b.glade"
     1418        builder = Gtk.Builder()
     1419        dbg('Loading '+gladefile)
     1420        builder.add_from_file(gladefile)
     1421        return builder
     1422    #
     1423    # Get objects from builder
     1424    #
     1425    def getobj(self,*args,**kwargs):
     1426        return self.builder.get_object(args[0])
     1427    ##############################
     1428    #
     1429    # Build database cache process
     1430    #
     1431    ##############################
     1432    #@trace
     1433    def do_cache(self,*args,**kwargs):
     1434        #
     1435        # Get complete filelist from filesystem
     1436        #
     1437        self.model.cache_info['msg']=_('Building file cache...')
     1438        files_cache = glob.glob('/usr/share/icons/**/*.png', recursive=True)
     1439        #
     1440        # Get current user Plank elements
     1441        #
     1442        self.model.cache_info['msg']="Searching local apps..."
     1443        self.model.cache_lists['local']=self.helper.search_local_plank(files_cache=files_cache)
     1444        #
     1445        # Get data from applications from /usr/share/application/*.desktop
     1446        #
     1447        self.model.cache_info['msg'] = "Searching system apps..."
     1448        self.model.cache_lists['system']=self.helper.search_in_applications(cache_info=self.model.cache_info,files_cache=files_cache)
     1449        #
     1450        # Purge possible duplicated entries
     1451        #
     1452        self.model.purge_duplicated_cache_items()
     1453        #
     1454        self.window.set_txt_status_bars('','','Detected apps: Plank({}) System({})'.format(len(self.model.cache_lists['local']),len(self.model.cache_lists['system'])))
     1455        self.helper.clean_status_bar(2)
     1456        #
     1457        # End cache process
     1458        #
     1459        self.model.cache_info['msg'] = _('Ready!')
     1460        self.model.cache_info['done']=True
     1461        #
     1462        # Hide level bar
     1463        #
     1464        self.window.change_stack('next',stack='bottom')
     1465
     1466    #
     1467    # Receives events from handler
     1468    #
     1469    #@trace
     1470    def process(self,*args,**kwargs):
     1471        try:
     1472            #
     1473            # Check arguments and unify commands
     1474            # Mandatory 'action'
     1475            # Optional: 'result': if it comes from callback
     1476            #           'to': changing windows from button menu
     1477            #
     1478            actions=[]
     1479            try:
     1480                check_args('action',**kwargs)
     1481            except Exception as e:
     1482                dbg(e)
     1483                sys.exit(1)
     1484
     1485            if type(kwargs['action']) == type(list()):
     1486                actions.extend(kwargs['action'])
     1487            else:
     1488                actions.append(kwargs['action'])
     1489            for op in args:
     1490                if type(op) == type(list()):
     1491                    actions.extend(op)
     1492                else:
     1493                    actions.append(op)
     1494
     1495            for action in actions:
     1496                if action == 'initialized':
     1497                    #
     1498                    # Run first actions
     1499                    #
     1500                    self.helper.do_call(self.window.update_status_bar,self.model.cache_info,None)
     1501                    self.helper.do_call(self.do_cache,{'action':'cache_completed'},self.process)
     1502
     1503                elif action == 'cache_completed':
     1504                    if self.model.is_validated:
     1505                        # when we are validated and don't have cache, screen shows loading
     1506                        # when cache_completed but not validated, do nothing, validation will update window
     1507                        self.window.change_stack('box_main_btn')
     1508
     1509                elif action == 'validation_init':
     1510                    #
     1511                    # Start the validation process
     1512                    #
     1513                    self.window.change_button_text(text='Checking',button='btn_validate')
     1514                    us,pw=self.window.get_entry('entry_usr','entry_pw')
     1515                    self.helper.n4d_validate(user=us,pwd=pw)
     1516
     1517                elif action == 'validation_end':
     1518                    #
     1519                    # Callback from n4d validation process
     1520                    #
     1521                    try:
     1522                        check_args('result', **kwargs)
     1523                    except Exception as e:
     1524                        dbg('N4D fail check system!')
     1525                        sys.exit(1)
     1526                    result = kwargs['result']
     1527                    self.validation_end(result)
     1528
     1529                elif action == 'goto':
     1530                    #
     1531                    # Button from main menu pressed
     1532                    #
     1533                    self.goto(*args,**kwargs)
     1534                elif action == 'refilter_elements':
     1535                    ##############################
     1536                    #
     1537                    # refilter when filter entry has changed
     1538                    #
     1539                    ##############################
     1540                    self.model.refilter(type='elements')
     1541                elif action == 'swap':
     1542                    ##############################
     1543                    #
     1544                    # change model & refilter when swaps elements
     1545                    #
     1546                    ##############################
     1547                    check_args('id',**kwargs)
     1548                    id=kwargs['id']
     1549                    self.applist_changed = True
     1550                    for e in self.model.store_elements:
     1551                        if e[APPCOL.ID]==id:
     1552                            e[APPCOL.SELECTED] = not e[APPCOL.SELECTED]
     1553                            break
     1554                    self.model.refilter(type='all')
     1555                    i=self.model.count_selected()
     1556                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
     1557                elif action == 'swap_locals':
     1558                    ##############################
     1559                    #
     1560                    # change model for local elements
     1561                    #
     1562                    ##############################
     1563                    self.applist_changed = True
     1564                    for e in self.model.store_elements:
     1565                        if e[APPCOL.TYPE]=='local':
     1566                            e[APPCOL.SELECTED] = True
     1567                    self.model.refilter(type='all')
     1568                    i=self.model.count_selected()
     1569                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
     1570                elif action == 'set_groups':
     1571                    ##############################
     1572                    #
     1573                    # callback from n4d_get_system_groups
     1574                    #
     1575                    ##############################
     1576                    try:
     1577                        check_args('result',**kwargs)
     1578                    except Exception as e:
     1579                        dbg('N4D fail check system!')
     1580                        sys.exit(1)
     1581                    self.model.group_list = []
     1582                    for g in [x['cn'][0] for x in kwargs['result']]:
     1583                        self.model.group_list.append(g)
     1584
     1585                elif action == 'set_profiles':
     1586                    ##############################
     1587                    #
     1588                    # callback from n4d_get_profiles_stored
     1589                    #
     1590                    ##############################
     1591                    try:
     1592                        check_args('result',**kwargs)
     1593                    except Exception as e:
     1594                        dbg('N4D fail check system!')
     1595                        sys.exit(1)
     1596                    self.model.profile_list = []
     1597                    for p in kwargs['result']:
     1598                        self.model.profile_list.append(p)
     1599                        for g in p[1].split(','):
     1600                            if g != '':
     1601                                self.model.current_relations[g]=p[0]
     1602
     1603                elif action == 'remove_profile_done':
     1604                    ##############################
     1605                    #
     1606                    # Callback from n4d_remove_profile
     1607                    #
     1608                    ##############################
     1609
     1610                    # Update information about existent profiles
     1611                    self.model.clear_liststore(type='profiles')
     1612                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
     1613                    self.window.change_stack('box_main_btn')
     1614
     1615                elif action == 'profile_is_updated':
     1616                    ##############################
     1617                    #
     1618                    # Callback from n4d_update_profile
     1619                    #
     1620                    ##############################
     1621
     1622                    # Update information about existent profiles
     1623                    self.model.clear_liststore(type='profiles')
     1624                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
     1625                    check_args('name',**kwargs)
     1626                    name = kwargs['name']
     1627                    # If this is a (single) callback from edit profiles
     1628                    if self.last_window != 'box_group_edit':
     1629                        self.reset_memories()
     1630                        self.window.set_txt_status_bars(name + _(' updated!'))
     1631                        self.helper.clean_status_bar(0)
     1632                        self.window.change_stack('box_main_btn')
     1633                    else:
     1634                    # In this is a (multiple or single) callback from profile-group change relation
     1635                        check_args('status_bar',**kwargs)
     1636                        bar = kwargs['status_bar']
     1637                        if bar == 'old':
     1638                            # old profile msg is always showed into first statusbar
     1639                            self.window.set_txt_status_bars(bar_info={'0': name + _(' updated!')})
     1640                            self.helper.clean_status_bar(0)
     1641                        if bar == 'new':
     1642                            # old profile msg is always showed into second statusbar
     1643                            self.window.set_txt_status_bars(bar_info={'1': name + _(' updated!')})
     1644                            self.helper.clean_status_bar(1)
     1645
     1646
     1647                elif action == 'profile_is_renamed':
     1648                    ##############################
     1649                    #
     1650                    # Callback from n4d_rename_profile
     1651                    #
     1652                    ##############################
     1653
     1654                    # Update information about existent profiles
     1655                    self.model.clear_liststore(type='profiles')
     1656                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
     1657                    # Force redo liststore from combobox from profile window, next time will rebuild the view
     1658                    for boxname in self.window.group_profile_boxes:
     1659                        self.window.group_profile_boxes[boxname][0].destroy()
     1660                        self.window.group_profile_boxes[boxname][1].destroy()
     1661                    self.window.group_profile_boxes = {}
     1662                    self.reset_memories()
     1663                    # Show msg into status bar
     1664                    check_args('oldname','newname', **kwargs)
     1665                    oldname = kwargs['oldname']
     1666                    newname = kwargs['newname']
     1667                    self.window.set_txt_status_bars(_('Updated profile from ')+oldname+_(' to ')+newname)
     1668                    self.helper.clean_status_bar(0)
     1669                    # Change user's view
     1670                    self.window.change_stack('box_main_btn')
     1671
     1672                elif action == 'load_profile_into_liststore':
     1673                    ##############################
     1674                    #
     1675                    # Callback from n4d_read_profile
     1676                    #
     1677                    ##############################
     1678                    check_args('name','result',**kwargs)
     1679                    apps_selected=kwargs['result']
     1680                    self.model.build_liststore(type='apps')
     1681                    self.model.load_profile_into_liststore(name=kwargs['name'], apps_selected=apps_selected)
     1682                    i = self.model.count_selected()
     1683                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
     1684                    self.window.set_entry({'entry_profile_name':kwargs['name']})
     1685                    self.reset_memories()
     1686                    self.user_is_editing=kwargs['name']
     1687                    self.window.change_stack('box_profiles')
     1688
     1689                elif action == 'change_relations':
     1690                    check_args('group','profile',**kwargs)
     1691                    group = kwargs['group']
     1692                    profile = kwargs['profile']
     1693                    oldrelation = ''
     1694                    if group in self.model.current_relations:
     1695                        oldrelation = self.model.current_relations[group]
     1696                    #
     1697                    # Sanity checks about relations
     1698                    #
     1699                    if self.check_sanity(group=group,profile=profile):
     1700                        self.update_profile_relations(group=group, profile=profile, oldrelation=oldrelation)
     1701
     1702
     1703            return
     1704        except Exception as e:
     1705            if not self.aborting_all_operations:
     1706                raise Exception(e)
     1707            else:
     1708                sys.exit(1)
     1709    ##############################
     1710    #
     1711    # Validation checking actions
     1712    #
     1713    ##############################
     1714    def validation_end(self,*args,**kwargs):
     1715        result = args[0]
     1716        self.window.change_button_text(text='Validate', button='btn_validate')
     1717        #
     1718        # Check the completed validation
     1719        #
     1720        return_str = result.split(' ')
     1721        if return_str[0].lower() == 'true' and return_str[1].lower() in ['admin', 'adm', 'admins', 'teachers']:
     1722            # Validation is done and sucessful
     1723            us, pw = self.window.get_entry('entry_usr', 'entry_pw')
     1724            self.model.user = us
     1725            self.model.pwd = pw
     1726            self.model.is_validated = True
     1727            #
     1728            # Get information about existent groups
     1729            #
     1730            self.helper.n4d_get_system_groups(user=us,pwd=pw)
     1731            #
     1732            # Get information about existent profiles
     1733            #
     1734            self.helper.n4d_get_profiles_stored(user=us,pwd=pw)
     1735            #
     1736            # Update content window
     1737            #
     1738            if self.model.cache_info['done']:
     1739                self.window.change_stack('box_main_btn')
     1740            else:
     1741                self.window.change_stack('box_loading')
     1742        else:
     1743            # Validation is failed
     1744            self.window.set_entry({'entry_usr': '', 'entry_pw': ''})
     1745            self.getobj('img_emoji').set_from_icon_name('face-crying', 100)
     1746    ##############################
     1747    #
     1748    # Change window
     1749    #
     1750    ##############################
     1751    def goto(self,*args,**kwargs):
     1752        check_args('to', **kwargs)
     1753        to = kwargs['to']
     1754        if to == 'main_window':
     1755            self.reset_memories()
     1756            self.window.set_txt_status_bars('')
     1757            self.window.change_stack('box_main_btn')
     1758        if to == 'new_profile':
     1759            self.window.set_entry({'entry_profile_name':''})
     1760            self.model.build_liststore(type='apps')
     1761            self.user_is_editing='new_profile'
     1762            self.window.change_stack('box_profiles')
     1763        if to == 'edit_profile':
     1764            self.model.build_liststore(type='profiles')
     1765            self.window.change_stack('box_pselector')
     1766        if to == 'group_mapping':
     1767            self.building_view = True
     1768            self.window.build_group_profile_window(groups=self.model.group_list,profiles=self.model.profile_list)
     1769            self.building_view = False
     1770            self.window.change_stack('box_group_edit')
     1771        if to == 'ask_remove':
     1772            check_args('id', **kwargs)
     1773            id = kwargs['id']
     1774            name_profile = self.model.profile_list[id][0]
     1775            txt="{} '{}'?".format(_('Are you sure to remove the profile'),name_profile)
     1776            self.window.set_entry({'lbl_msg_dialog':txt})
     1777            self.window.change_stack('box_dialog')
     1778        if to == 'editing_profile':
     1779            check_args('id', **kwargs)
     1780            id = kwargs['id']
     1781            name_profile=self.model.profile_list[id][0]
     1782            self.helper.n4d_read_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
     1783        if to == 'remove_confirmed':
     1784            check_args('id', **kwargs)
     1785            id = kwargs['id']
     1786            name_profile = self.model.profile_list[id][0]
     1787            self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
     1788        if to == 'back':
     1789            #
     1790            # Check if need to save something
     1791            #
     1792            if self.last_window == 'box_profiles':
     1793                if self.user_is_editing == 'new_profile':
     1794                    if self.profile_name_changed or self.applist_changed:
     1795                        pname = self.window.get_entry('entry_profile_name')[0]
     1796                        if not self.model.check_profile_name(name=pname,type='available'):
     1797                            self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
     1798                            self.window.change_stack('box_warning')
     1799                            return
     1800                        # Store new profile
     1801                        self.update_current_profile()
     1802                        return
     1803                elif self.user_is_editing != '':
     1804                    # User is editing existent profile
     1805                    if self.applist_changed and not self.profile_name_changed:
     1806                        # Update current profile (self.user_is_editing)
     1807                        self.update_current_profile()
     1808                    if not self.applist_changed and self.profile_name_changed:
     1809                        # Rename current profile (self.user_is_editing)
     1810                        newname=self.window.get_entry('entry_profile_name')[0]
     1811                        if not self.model.check_profile_name(name=newname,type='available'):
     1812                            self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
     1813                            self.window.change_stack('box_warning')
     1814                            return
     1815                        self.helper.n4d_rename_profile(user=self.model.user,pwd=self.model.pwd,old=self.user_is_editing,new=newname)
     1816                    if self.applist_changed and self.profile_name_changed:
     1817                        # create new profile & remove old profile (self.user_is_editing)
     1818                        self.update_current_profile()
     1819                        #
     1820                        # -Uncomment if it's desired to remove old profile when modifications of name and apps are done
     1821                        # -If commented, new profile it's created making a copy of the current profile with other name
     1822                        # and remove profile only it's possible from the remove menu, doing more explicitly the remove action
     1823                        #
     1824                        #self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing)
     1825                    if self.applist_changed or self.profile_name_changed:
     1826                        return
     1827
     1828            # if nothing is saved do normal go back
     1829            self.model.n_selected = 0
     1830            self.window.set_txt_status_bars('','')
     1831            self.window.change_stack('back')
     1832        return
     1833
     1834    ##############################
     1835    #
     1836    # Read store & create or update a profile
     1837    #
     1838    ##############################
     1839    def update_current_profile(self,*args,**kwargs):
     1840        profile_name = self.window.get_entry('entry_profile_name')[0]
     1841        if self.user_is_editing == 'new_profile' and not self.model.check_profile_name(name=profile_name,type='available'):
     1842            self.window.set_entry({'lbl_msg_warning':_('Profile name not valid')})
     1843            self.window.change_stack('box_warning')
     1844            self.reset_memories()
     1845            return
     1846        applist = self.model.get_liststore_selections(type='apps')
     1847        grp = self.model.get_group_from_profile(nameprofile=profile_name)
     1848        glist = []
     1849        if grp != None:
     1850            glist.append(grp)
     1851
     1852        self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=profile_name,grouplist=glist,applist=applist)
     1853        return
     1854
     1855    ##############################
     1856    #
     1857    # Update profile assigning one group when changed on combo and it's different
     1858    #
     1859    ##############################
     1860    def update_profile_relations(self,*args,**kwargs):
     1861        try:
     1862            #
     1863            # First call acts as a caller from N4D method setting itself as a callback method
     1864            # if haven't group & profile parameters, method is doing callback with 'result' parameter
     1865            #
     1866            check_args('group', 'profile','oldrelation', **kwargs)
     1867            group = kwargs['group']
     1868            profile_selected = kwargs['profile']
     1869            old_profile = kwargs['oldrelation']
     1870            #
     1871            # Check if it's needed to update old profile removing relation
     1872            #
     1873            if self.model.check_profile_name(name=old_profile,type='valid'):
     1874                #
     1875                # need to kwown the current apps and other groups applied from profile before updating
     1876                #
     1877                oldgroups = []
     1878                for p in self.model.profile_list:
     1879                    pname = p[0]
     1880                    pgrps = p[1]
     1881                    if pname == old_profile:
     1882                        for g in pgrps.split(','):
     1883                            if g != group and g != '':
     1884                                oldgroups.append(g)
     1885                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=old_profile,
     1886                                             noprocess=self.update_profile_relations,
     1887                                             extraparam={'grouplist': oldgroups,'status_bar':'old'})
     1888            if self.model.check_profile_name(name=profile_selected,type='valid'):
     1889                #
     1890                # need to kwown the current apps and other groups applied from profile before updating
     1891                #
     1892                oldgroups = []
     1893                for p in self.model.profile_list:
     1894                    pname = p[0]
     1895                    pgrps = p[1]
     1896                    if pname == profile_selected:
     1897                        for g in pgrps.split(','):
     1898                            if g != group and g != '':
     1899                                oldgroups.append(g)
     1900                newglist = [group]
     1901                newglist.extend(oldgroups)
     1902                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=profile_selected,
     1903                                             noprocess=self.update_profile_relations,
     1904                                             extraparam={'grouplist': newglist,'status_bar':'new'})
     1905        except Exception as e:
     1906            #
     1907            # Second call of this function, when callback from n4d_read_profile is done, 'result' is set into parameters with list applications of profile
     1908            #
     1909            check_args('grouplist','result','name','status_bar',**kwargs)
     1910            applist = kwargs['result']
     1911            grouplist = kwargs['grouplist']
     1912            name = kwargs['name']
     1913            status_bar = kwargs['status_bar']
     1914
     1915            self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,applist=applist,name=name, grouplist=grouplist, status_bar=status_bar)
     1916            # After this call return to normal callback and will update profile list with new relations
     1917        return
     1918
     1919    ##############################
     1920    #
     1921    # Sanity logic about relations
     1922    #
     1923    ##############################
     1924    def check_sanity(self,*args,**kwargs):
     1925        check_args('group','profile',**kwargs)
     1926        group = kwargs['group']
     1927        profile = kwargs['profile']
     1928
     1929        if not group:
     1930            return False
     1931
     1932        # Existence flags
     1933        ret_gr = False
     1934
     1935        for g in self.model.group_list:
     1936            if g == group:
     1937                # Group must exist
     1938                ret_gr = True
     1939                break
     1940        for p in self.model.profile_list:
     1941            pname = p[0]
     1942            pgroup = p[1]
     1943
     1944            for g in pgroup.split(','):
     1945                if group == g:
     1946                    # Fail if already relationed
     1947                    if pname == profile:
     1948                        return False
     1949
     1950        return ret_gr
     1951
     1952
     1953
     1954##############################
     1955#
     1956# Main program
     1957#
     1958##############################
     1959
     1960GObject.threads_init()
    8421961if __name__ == "__main__":
    843     localpath=os.path.dirname(os.path.realpath(__file__))
    844     win = MainWindow(localpath='/usr/lib/syncer-plank')
     1962    ctl = Controller(localpath=os.path.dirname(os.path.realpath(__file__)))
    8451963    sys.exit(Gtk.main())
    8461964
Note: See TracChangeset for help on using the changeset viewer.