source: n4dgtklogin/trunk/fuentes/python3-n4dgtklogin.install/edupals/ui/n4dgtklogin.py @ 7506

Last change on this file since 7506 was 7506, checked in by Juanma, 19 months ago

added vertical orientation (experimental)

File size: 12.1 KB
Line 
1#!/usr/bin/env python3
2###
3#This class returns a login_grid whith a standarized login form
4###
5import os,sys,socket
6import threading
7import gi
8gi.require_version('Gtk', '3.0')
9gi.require_version('PangoCairo', '1.0')
10import json
11import cairo
12#import commands
13from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib, PangoCairo, Pango
14try:
15        import xmlrpc.client as n4d
16except ImportError:
17        raise ImportError("xmlrpc not available. Disabling server queries")
18import ssl
19import time
20import gettext
21
22gettext.textdomain('edupals.ui.common')
23_ = gettext.gettext
24GObject.threads_init()
25
26class N4dGtkLogin(Gtk.Box):
27        __gtype_name__='n4dgtklogin'
28
29        def __init__(self,*args,**kwds):
30                super().__init__(*args,**kwds)
31                self.vertical=False
32                self.sw_n4d=True
33                if hasattr(sys,'last_value'):
34                #If there's any error at this point it only could be an ImportError caused by xmlrpc
35                        self.sw_n4d=False
36                self.css_classes={}
37                self.style_provider=Gtk.CssProvider()
38                Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),self.style_provider,Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
39                self.default_spacing=12
40                self.username_placeholder=_("Username")
41                self.server_placeholder=_("Server IP (Default value : server)")
42                self.login_banner_default="llx-avatar"
43                self.info_banner=None
44                self.allowed_groups=[]
45                #internal boxes
46                self.form_box=Gtk.Box(spacing=0,orientation=Gtk.Orientation.VERTICAL)
47                self.info_box=Gtk.Box(spacing=self.default_spacing,orientation=Gtk.Orientation.VERTICAL)
48                self.main_grid=Gtk.Grid()
49                self.render_form()
50        #def __init__
51
52        def set_vertical(self,vertical=True):
53                if type(vertical)!=type(bool()):
54                        vertical=True
55                self.vertical=vertical
56                self.set_mw_proportion_ratio(2,1)
57        #def set_vertical
58
59        def set_allowed_groups(self,groups):
60                self.allowed_groups=groups
61        #def set_allowed_groups
62
63        def set_mw_proportion_ratio(self,left_panel,right_panel):
64                for child in self.main_grid.get_children():
65                        self.main_grid.remove(child)
66                if self.vertical:
67                        self.main_grid.attach(self.info_box,0,0,1,left_panel)
68                        self.main_grid.attach(self.form_box,0,0+left_panel,1,right_panel)
69                else:
70                        self.main_grid.attach(self.info_box,0,0,left_panel,1)
71                        self.main_grid.attach(self.form_box,0+left_panel,0,right_panel,1)
72        #def set_mw_proportion_ratio
73       
74        def set_mw_background(self,image=None,cover=False,from_color='#ffffff',to_color='silver',gradient='linear'):
75                mw_background=self._set_background(image,cover,from_color,to_color,gradient)
76                self.css_classes['#mw']='{'+mw_background+';;}'
77                self._set_css()
78        #def set_mw_background
79
80        def set_login_background(self,image=None,cover=False,from_color='#ffffff',to_color='@silver',gradient='linear'):
81                form_background=self._set_background(image,cover,from_color,to_color,gradient)
82                self.css_classes['#main']='{'+form_background+';;}'
83                self._set_css()
84        #def set_login_background
85
86        def set_info_background(self,image=None,cover=False,from_color='#ffffff',to_color='@silver',gradient='linear'):
87                info_background=self._set_background(image,cover,from_color,to_color,gradient)
88                self.css_classes['#info']='{'+info_background+';;}'
89                self._set_css()
90        #def set_info_background
91
92        def _set_css(self):
93                css=''
94                for css_class,style in self.css_classes.items():
95                        css=css+css_class+' '+style
96                css_style=eval('b"""'+css+'"""')
97                self.style_provider.load_from_data(css_style)
98        #def _set_css
99
100        def _set_background(self,image=None,cover=False,from_color='#ffffff',to_color='silver',gradient='linear'):
101                bg=''
102                if image and os.path.isfile(image):
103                        if cover:
104                                bg='background-image:url("'+image+'"); background-repeat:no-repeat; background-size:100% 100%'
105                        else:
106                                bg='background-image:url("'+image+'"); background-repeat:no-repeat;'
107                elif image:
108                        #try to locate the image in the default theme
109                        icon_theme=Gtk.IconTheme.get_default()
110                        icon_sizes=icon_theme.get_icon_sizes(image)
111                        if icon_sizes:
112                                max_size=max(icon_sizes)
113                                icon=icon_theme.lookup_icon(image,max_size,0)
114                                icon_path=icon.get_filename()
115                                bg='background-image:url("'+icon_path+'"); background-repeat:no-repeat; background-size:100% 100%'
116
117                else:
118                        if gradient=='linear':
119                                bg='background-image:-gtk-gradient (linear, left top, left bottom, from ('+from_color+'),  to ('+to_color+'))'
120                        elif gradient=='radial':
121                                bg='background-image:-gtk-gradient (radial, center center,0,center center,1, from ('+from_color+'),  to ('+to_color+'))'
122                return bg
123        #def _set_background
124
125        def set_default_username(self,username):
126                self.username_placeholder=username
127                self._set_text_for_entry(self.txt_username,username)
128        #def set_default_username
129
130        def set_default_server(self,server):
131                self.server_placeholder=server
132                if self.txt_server:
133                        self._set_text_for_entry(self.txt_server,server)
134        #def set_default_server
135       
136        def _set_text_for_entry(self,widget,text):
137                widget.set_placeholder_text(text)
138        #def _set_text_for_entry
139
140        def _lookup_user_face(self):
141                sw_ok=False
142                if os.path.isfile(os.path.expanduser('~/.face')):
143                        sw_ok=True
144                        self.set_login_banner(os.path.expanduser('~/.face'))
145                return sw_ok
146        #def _lookup_user_face
147
148        def set_login_banner(self,banner):
149                self.login_banner.set_from_pixbuf(self._get_image(banner))
150        #def set_banner
151
152        def set_info_banner(self,banner,x=72,y=72):
153                self.info_banner.set_from_pixbuf(self._get_image(banner))
154        #def set_info_banner
155
156        def _get_image(self,image,x=72,y=72):
157                icon_theme=Gtk.IconTheme.get_default()
158                if icon_theme.has_icon(image):
159                        pixbuf=icon_theme.load_icon(image,x,0)
160                else:
161                        if os.path.isfile(image):
162                                pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_scale(image,x,y,True)
163                return pixbuf
164        #def _get_image
165       
166        def set_info_text(self,title,subtitle,text):
167                sw_ok=True
168                info_msg=''
169                self.lbl_info_msg.set_width_chars(25)
170                self.lbl_info_msg.set_max_width_chars(25)
171                try:
172                        msg="<b><big>"+title+"</big></b>"
173                        info_msg=msg+'\n'+subtitle+'\n\n'+text
174                except Exception as e:
175                        sw_ok=False
176                        print(e)
177                self.lbl_info_msg.set_markup(info_msg)
178                self.lbl_info_msg.show()
179                return sw_ok
180        #def set_info_text
181
182        def hide_server_entry(self):
183                self.txt_server.props.no_show_all=True
184                self.txt_server.hide()
185        #def hide_server_entry
186
187        def hide_info_box(self):
188                self.info_box.props.no_show_all=True
189                self.info_box.hide()
190        #def hide_info_box
191
192        def get_action_area(self):
193                return self.info_box
194        #def get_action_area
195
196        def render_form(self):
197                self.main_grid.set_hexpand(True)
198                self.main_grid.set_vexpand(True)
199                self.main_grid.set_column_homogeneous(True)
200                self.main_grid.set_row_homogeneous(True)
201                self._render_login_form()
202                self._render_info_form()
203                if self.vertical:
204                        self.main_grid.attach(self.info_box,1,1,1,1)
205                        self.main_grid.attach(self.form_box,1,3,1,1)
206                else:
207                        self.main_grid.attach(self.info_box,1,1,2,1)
208                        self.main_grid.attach(self.form_box,3,1,1,1)
209                self.pack_start(self.main_grid,True,True,0)
210                self.set_name("mw")
211                self.form_box.set_name("main")
212        #def render_form
213
214        def _render_login_form(self):
215                self.txt_username=Gtk.Entry()
216                self.login_banner=Gtk.Image()
217                if not self._lookup_user_face():
218                        self.set_login_banner(self.login_banner_default)
219                self.login_banner.set_margin_bottom(self.default_spacing)
220                self.set_default_username(self.username_placeholder)
221                self.txt_username.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"emblem-personal")
222                self.txt_password=Gtk.Entry()
223                self.txt_password.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"badge-small")
224                self._set_text_for_entry(self.txt_password,_("Password"))
225                self.txt_server=Gtk.Entry()
226                self.set_default_server(self.server_placeholder)
227                self.txt_server.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,"server")
228                self.btn_sign=Gtk.Button(stock=Gtk.STOCK_OK)
229                self.btn_sign.connect('clicked',self._validate)
230                self.frame=Gtk.Frame()
231               
232                self.frame.set_shadow_type(Gtk.ShadowType.OUT)   
233                login_grid=Gtk.Grid()
234                login_grid.set_margin_left(self.default_spacing)
235                login_grid.set_margin_right(self.default_spacing)
236                login_grid.set_margin_top(self.default_spacing)
237                login_grid.set_margin_bottom(self.default_spacing)
238                self.spinner=Gtk.Spinner()
239                color=Gdk.Color(0,0,1)
240                self.spinner.modify_bg(Gtk.StateType.NORMAL,color)
241                login_grid.attach(self.spinner,0,1,1,5)
242                login_grid.attach(self.login_banner,0,0,1,1)
243                login_grid.attach(self.txt_username,0,1,1,1)
244                self._set_widget_default_props(self.txt_username,_("Username"))
245                self.txt_username.connect('activate',self._validate)
246                login_grid.attach(self.txt_password,0,2,1,1)
247                self._set_widget_default_props(self.txt_password,_("Password"))
248                self.txt_password.set_visibility(False)
249                self.txt_password.props.caps_lock_warning=True
250                self.txt_password.connect('activate',self._validate)
251                login_grid.attach(self.txt_server,0,3,1,1)
252                self._set_widget_default_props(self.txt_server,_("Master Server IP"))
253                self.txt_server.connect('activate',self._validate)
254                self.btn_sign.set_margin_top(self.default_spacing)
255                login_grid.attach(self.btn_sign,0,4,1,1)
256                self.sta_info=Gtk.InfoBar()
257                self.sta_info.set_show_close_button(True)
258                self.sta_info.set_message_type(Gtk.MessageType.ERROR)
259                self.lbl_error=Gtk.Label(_("Login failed"))
260                self.sta_info.get_action_area().add(self.lbl_error)
261                self.sta_info.set_visible(False)
262                self.sta_info.set_no_show_all(True)
263                self.sta_info.connect('response',self._status_info_hide)
264                self.sta_info.set_valign(True)
265                login_grid.props.valign=Gtk.Align.CENTER
266                login_grid.props.halign=Gtk.Align.CENTER
267                self.form_box.pack_start(self.sta_info,False,True,0)
268                self.form_box.pack_start(login_grid,True,True,0)
269        #def _render_login_form
270
271        def _render_info_form(self):
272                self.info_box.set_homogeneous(False)
273                self.info_banner=Gtk.Image()
274                info_detail_box=Gtk.Box(spacing=self.default_spacing,orientation=Gtk.Orientation.VERTICAL)
275                info_detail_box.pack_start(self.info_banner,False,False,0)
276                self.lbl_info_msg=Gtk.Label()
277                self.lbl_info_msg.set_use_markup(True)
278                self.lbl_info_msg.set_line_wrap(True)
279                info_detail_box.pack_start(self.lbl_info_msg,True,True,0)
280                self.css_classes['#label']='{background-color:rgba(200,200,200,0.8);;}'
281                self.lbl_info_msg.set_name("label")
282                self.lbl_info_msg.set_no_show_all(True)
283                self.lbl_info_msg.hide()
284                self.info_box.set_name("info")
285                info_detail_box.props.valign=Gtk.Align.CENTER
286                info_detail_box.props.halign=Gtk.Align.CENTER
287                self.info_box.pack_start(info_detail_box,True,True,0)
288                self._set_css()
289        #def _render_info_form
290
291        def _set_widget_default_props(self,widget,tooltip=None):
292                widget.set_valign(True)
293                widget.set_halign(True)
294                widget.set_tooltip_text(tooltip)
295        #def _set_widget_default_props
296
297        def _status_info_hide(self,widget,data):
298                self.sta_info.hide()
299                self.frame.set_sensitive(True)
300        #def _info_hide
301
302        def _validate(self,widget=None):
303                user=self.txt_username.get_text()
304                pwd=self.txt_password.get_text()
305                server=self.txt_server.get_text()
306                if not server:
307                        server='server'
308                        try:
309                                socket.gethostbyname(server)
310                        except:
311                                server='localhost'
312                self.spinner.start()
313                self.frame.set_sensitive(False)
314                th=threading.Thread(target=self._t_validate,args=[user,pwd,server])
315                th.start()
316        #def _validate
317
318        def _t_validate(self,user,pwd,server):
319                ret=[False]
320                self.lbl_error.set_text(_("Login failed"))
321                if self.sw_n4d:
322                        try:
323                                self.n4dclient=self._n4d_connect(server)
324                                ret=self.n4dclient.validate_user(user,pwd)
325                        except socket.error as e:
326                                self.lbl_error.set_text(_("Unknown host"))
327                                ret=[False,str(e)]
328
329                self.spinner.stop()
330                if not ret[0]:
331                        self.sta_info.show()
332                        self.lbl_error.show()
333                        #show server entry if we can't connect to n4d in "server"
334                        self.txt_server.props.no_show_all=False
335                        self.txt_server.show()
336                elif self.allowed_groups and not set(self.allowed_groups).intersection(ret[1]):
337                        #Check user groups
338                        self.lbl_error.set_text(_("User not allowed"))
339                        self.sta_info.show()
340                        self.lbl_error.show()
341                else:
342                        GLib.idle_add(self.after_validate,user,pwd,server)
343                #local validation
344        #def _t_validate
345
346        def after_validation_goto(self,func,data=None):
347                self.after_validate=func
348        #def after_validation_func
349
350        def _n4d_connect(self,server):
351                context=ssl._create_unverified_context()
352                try:
353                        socket.gethostbyname(server)
354                except:
355                        #It could be an ip
356                        try:
357                                socket.inet_aton(server)
358                        except Exception as e:
359                                print(e)
360                                raise
361                c = n4d.ServerProxy("https://"+server+":9779",context=context,allow_none=True)
362                return c
363        #def _n4d_connect
Note: See TracBrowser for help on using the repository browser.