2 Copyright (C) 1997 Thomas Tanghus (tanghus@earthling.net)
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include "kdm-users.h"
26 #include <KIconDialog>
27 #include <KImageFilePreview>
29 #include <KIO/NetAccess>
32 #include <KMessageBox>
34 #include <KConfigGroup>
35 #include <KStandardDirs>
36 #include <KStandardGuiItem>
38 #include <QButtonGroup>
41 #include <QDragEnterEvent>
45 #include <QIntValidator>
47 #include <QPushButton>
48 #include <QRadioButton>
49 #include <QStackedWidget>
51 #include <QGridLayout>
52 #include <QHBoxLayout>
53 #include <QVBoxLayout>
55 #include <sys/types.h>
60 extern KConfig
*config
;
62 KDMUsersWidget::KDMUsersWidget( QWidget
*parent
)
63 : QWidget( parent
), m_readOnly( false )
67 if (!stat( "/etc/debian_version", &st
)) { /* debian */
70 } else if (!stat( "/usr/portage", &st
)) { /* gentoo */
73 } else if (!stat( "/etc/mandrake-release", &st
)) { /* mandrake - check before redhat! */
76 } else if (!stat( "/etc/redhat-release", &st
)) { /* redhat */
79 } else /* if (!stat( "/etc/SuSE-release", &st )) */ { /* suse */
88 // We assume that $kde_datadir/kdm exists, but better check for pics/ and pics/users,
89 // and create them if necessary.
90 m_userPixDir
= config
->group( "X-*-Greeter" ).readEntry( "FaceDir", KStandardDirs::installPath( "data" ) + "kdm/faces" ) + '/';
91 QDir
testDir( m_userPixDir
);
92 if (!testDir
.exists() && !testDir
.mkdir( testDir
.absolutePath() ) && !geteuid())
93 KMessageBox::sorry( this, i18n("Unable to create folder %1", testDir
.absolutePath() ) );
94 if (!getpwnam( "nobody" ) && !geteuid())
95 KMessageBox::sorry( this, i18n(
96 "User 'nobody' does not exist. "
97 "Displaying user images will not work in KDM.") );
99 m_defaultText
= i18n("<placeholder>default</placeholder>");
103 minGroup
= new QGroupBox( i18nc(
104 "@title:group UIDs belonging to system users like 'cron'", "System U&IDs"), this );
105 minGroup
->setWhatsThis( i18n(
106 "Users with a UID (numerical user identification) outside this range "
107 "will not be listed by KDM and this setup dialog. "
108 "Note that users with the UID 0 (typically root) are not affected by "
109 "this and must be explicitly excluded in \"Inverse selection\" mode.") );
110 QSizePolicy
sp_ign_fix( QSizePolicy::Ignored
, QSizePolicy::Fixed
);
111 QValidator
*valid
= new QIntValidator( 0, 999999, minGroup
);
112 QLabel
*minlab
= new QLabel( i18nc("UIDs", "Below:"), minGroup
);
113 leminuid
= new KLineEdit( minGroup
);
114 minlab
->setBuddy( leminuid
);
115 leminuid
->setSizePolicy( sp_ign_fix
);
116 leminuid
->setValidator( valid
);
117 connect( leminuid
, SIGNAL(textChanged( const QString
& )), SIGNAL(changed()) );
118 connect( leminuid
, SIGNAL(textChanged( const QString
& )), SLOT(slotMinMaxChanged()) );
119 QLabel
*maxlab
= new QLabel( i18nc("UIDs", "Above:"), minGroup
);
120 lemaxuid
= new KLineEdit( minGroup
);
121 maxlab
->setBuddy( lemaxuid
);
122 lemaxuid
->setSizePolicy( sp_ign_fix
);
123 lemaxuid
->setValidator( valid
);
124 connect( lemaxuid
, SIGNAL(textChanged( const QString
& )), SIGNAL(changed()) );
125 connect( lemaxuid
, SIGNAL(textChanged( const QString
& )), SLOT(slotMinMaxChanged()) );
126 QGridLayout
*grid
= new QGridLayout( minGroup
);
127 grid
->addWidget( minlab
, 0, 0 );
128 grid
->addWidget( leminuid
, 0, 1 );
129 grid
->addWidget( maxlab
, 1, 0 );
130 grid
->addWidget( lemaxuid
, 1, 1 );
132 usrGroup
= new QGroupBox( i18nc("@title:group", "Users"), this );
133 cbshowlist
= new QCheckBox( i18nc("... of users", "Show list"), usrGroup
);
134 cbshowlist
->setWhatsThis( i18n(
135 "If this option is checked, KDM will show a list of users, so users can "
136 "click on their name or image rather than typing in their login.") );
137 cbcomplete
= new QCheckBox( i18nc("user ...", "Autocompletion"), usrGroup
);
138 cbcomplete
->setWhatsThis( i18n(
139 "If this option is checked, KDM will automatically complete "
140 "user names while they are typed in the line edit.") );
141 cbinverted
= new QCheckBox( i18nc(
142 "@option:check mode of the user selection", "Inverse selection"), usrGroup
);
143 cbinverted
->setWhatsThis( i18n(
144 "This option specifies how the users for \"Show list\" and \"Autocompletion\" "
145 "are selected in the \"Select users and groups\" list: "
146 "If not checked, select only the checked users. "
147 "If checked, select all non-system users, except the checked ones.") );
148 cbusrsrt
= new QCheckBox( i18n("Sor&t users"), usrGroup
);
149 cbusrsrt
->setWhatsThis( i18n(
150 "If this is checked, KDM will alphabetically sort the user list. "
151 "Otherwise users are listed in the order they appear in the password file.") );
152 QButtonGroup
*buttonGroup
= new QButtonGroup( usrGroup
);
153 buttonGroup
->setExclusive( false );
154 connect( buttonGroup
, SIGNAL(buttonClicked( int )), SLOT(slotShowOpts()) );
155 connect( buttonGroup
, SIGNAL(buttonClicked( int )), SIGNAL(changed()) );
156 buttonGroup
->addButton( cbshowlist
);
157 buttonGroup
->addButton( cbcomplete
);
158 buttonGroup
->addButton( cbinverted
);
159 buttonGroup
->addButton( cbusrsrt
);
160 QBoxLayout
*box
= new QVBoxLayout( usrGroup
);
161 box
->addWidget( cbshowlist
);
162 box
->addWidget( cbcomplete
);
163 box
->addWidget( cbinverted
);
164 box
->addWidget( cbusrsrt
);
166 wstack
= new QStackedWidget( this );
167 s_label
= new QLabel( i18n("S&elect users and groups:"), this );
168 s_label
->setBuddy( wstack
);
169 optinlv
= new K3ListView( this );
170 optinlv
->addColumn( i18n("Selected Users") );
171 optinlv
->setResizeMode( Q3ListView::LastColumn
);
172 optinlv
->setWhatsThis( i18n(
173 "KDM will show all checked users. Entries denoted with '@' are user groups. "
174 "Checking a group is like checking all users in that group.") );
175 wstack
->addWidget( optinlv
);
176 connect( optinlv
, SIGNAL(clicked( Q3ListViewItem
* )),
177 SLOT(slotUpdateOptIn( Q3ListViewItem
* )) );
178 connect( optinlv
, SIGNAL(clicked( Q3ListViewItem
* )),
180 optoutlv
= new K3ListView( this );
181 optoutlv
->addColumn( i18n("Excluded Users") );
182 optoutlv
->setResizeMode( Q3ListView::LastColumn
);
183 optoutlv
->setWhatsThis( i18n(
184 "KDM will show all non-checked non-system users. Entries denoted with '@' "
185 "are user groups. Checking a group is like checking all users in that group.") );
186 wstack
->addWidget( optoutlv
);
187 connect( optoutlv
, SIGNAL(clicked( Q3ListViewItem
* )),
188 SLOT(slotUpdateOptOut( Q3ListViewItem
* )) );
189 connect( optoutlv
, SIGNAL(clicked( Q3ListViewItem
* )),
192 faceGroup
= new QGroupBox( i18nc(
193 "@title:group source for user faces", "User Image Source"), this );
194 faceGroup
->setWhatsThis( i18n(
195 "Here you can specify where KDM will obtain the images that represent users. "
196 "\"System\" represents the global folder; these are the pictures you can set below. "
197 "\"User\" means that KDM should read the user's $HOME/.face.icon file. "
198 "The two selections in the middle define the order of preference if both sources are available.") );
199 rbadmonly
= new QRadioButton( i18nc("@option:radio image source", "System"), faceGroup
);
200 rbprefadm
= new QRadioButton( i18nc("@option:radio image source", "System, user"), faceGroup
);
201 rbprefusr
= new QRadioButton( i18nc("@option:radio image source", "User, system"), faceGroup
);
202 rbusronly
= new QRadioButton( i18nc("@option:radio image source", "User"), faceGroup
);
203 buttonGroup
= new QButtonGroup( faceGroup
);
204 connect( buttonGroup
, SIGNAL(buttonClicked( int )), SLOT(slotFaceOpts()) );
205 connect( buttonGroup
, SIGNAL(buttonClicked( int )), SIGNAL(changed()) );
206 buttonGroup
->addButton( rbadmonly
);
207 buttonGroup
->addButton( rbprefadm
);
208 buttonGroup
->addButton( rbprefusr
);
209 buttonGroup
->addButton( rbusronly
);
210 box
= new QVBoxLayout( faceGroup
);
211 box
->addWidget( rbadmonly
);
212 box
->addWidget( rbprefadm
);
213 box
->addWidget( rbprefusr
);
214 box
->addWidget( rbusronly
);
216 QGroupBox
*picGroup
= new QGroupBox( i18nc(
217 "@title:group user face assignments", "User Images"), this );
218 usercombo
= new KComboBox( picGroup
);
219 usercombo
->setWhatsThis( i18n("The user the image below belongs to.") );
220 connect( usercombo
, SIGNAL(activated( int )),
221 SLOT(slotUserSelected()) );
222 QLabel
*userlabel
= new QLabel( i18n("User:"), picGroup
);
223 userlabel
->setBuddy( usercombo
);
224 userbutton
= new QPushButton( picGroup
);
225 userbutton
->setAcceptDrops( true );
226 userbutton
->installEventFilter( this ); // for drag and drop
227 uint sz
= style()->pixelMetric( QStyle::PM_ButtonMargin
) * 2 + 48;
228 userbutton
->setFixedSize( sz
, sz
);
229 connect( userbutton
, SIGNAL(clicked()),
230 SLOT(slotUserButtonClicked()) );
231 userbutton
->setToolTip( i18n("Click or drop an image here") );
232 userbutton
->setWhatsThis( i18n(
233 "Here you can see the image assigned to the user selected in the combo "
234 "box above. Click on the image button to select from a list of images "
235 "or drag and drop your own image on to the button (e.g. from Konqueror).") );
236 rstuserbutton
= new QPushButton( i18nc(
237 "@action:button assign default user face", "R&eset"), picGroup
);
238 rstuserbutton
->setWhatsThis( i18n(
239 "Click this button to make KDM use the default image for the selected user.") );
240 connect( rstuserbutton
, SIGNAL(clicked()),
241 SLOT(slotUnsetUserPix()) );
242 QGridLayout
*hlpl
= new QGridLayout( picGroup
);
243 hlpl
->setSpacing( KDialog::spacingHint() );
244 hlpl
->addWidget( userlabel
, 0, 0 );
245 hlpl
->addWidget( usercombo
, 0, 1 ); // XXX this makes the layout too wide
246 hlpl
->addWidget( userbutton
, 1, 0, 1, 2, Qt::AlignHCenter
);
247 hlpl
->addWidget( rstuserbutton
, 2, 0, 1, 2, Qt::AlignHCenter
);
249 QHBoxLayout
*main
= new QHBoxLayout( this );
250 main
->setSpacing( 10 );
252 QVBoxLayout
*lLayout
= new QVBoxLayout();
253 main
->addItem( lLayout
);
254 lLayout
->setSpacing( 10 );
255 lLayout
->addWidget( minGroup
);
256 lLayout
->addWidget( usrGroup
);
257 lLayout
->addStretch( 1 );
259 QVBoxLayout
*mLayout
= new QVBoxLayout();
260 main
->addItem( mLayout
);
261 mLayout
->setSpacing( 10 );
262 mLayout
->addWidget( s_label
);
263 mLayout
->addWidget( wstack
);
264 mLayout
->setStretchFactor( wstack
, 1 );
265 main
->setStretchFactor( mLayout
, 1 );
267 QVBoxLayout
*rLayout
= new QVBoxLayout();
268 main
->addItem( rLayout
);
269 rLayout
->setSpacing( 10 );
270 rLayout
->addWidget( faceGroup
);
271 rLayout
->addWidget( picGroup
);
272 rLayout
->addStretch( 1 );
276 void KDMUsersWidget::makeReadOnly()
279 leminuid
->setReadOnly( true );
280 lemaxuid
->setReadOnly( true );
281 cbshowlist
->setEnabled( false );
282 cbcomplete
->setEnabled( false );
283 cbinverted
->setEnabled( false );
284 cbusrsrt
->setEnabled( false );
285 rbadmonly
->setEnabled( false );
286 rbprefadm
->setEnabled( false );
287 rbprefusr
->setEnabled( false );
288 rbusronly
->setEnabled( false );
289 wstack
->setEnabled( false );
290 disconnect( userbutton
, SIGNAL(clicked()), this, SLOT(slotUserButtonClicked()) );
291 userbutton
->setAcceptDrops( false );
292 rstuserbutton
->setEnabled( false );
295 void KDMUsersWidget::slotShowOpts()
297 bool en
= !m_readOnly
&& ( cbshowlist
->isChecked() || cbcomplete
->isChecked() );
298 cbinverted
->setEnabled( en
);
299 cbusrsrt
->setEnabled( en
);
300 wstack
->setEnabled( en
);
301 wstack
->setCurrentWidget( cbinverted
->isChecked() ? optoutlv
: optinlv
);
302 en
= cbshowlist
->isChecked();
303 faceGroup
->setEnabled( en
);
305 usercombo
->setEnabled( false );
306 userbutton
->setEnabled( false );
307 rstuserbutton
->setEnabled( false );
312 void KDMUsersWidget::slotFaceOpts()
314 bool en
= !m_readOnly
&& !rbusronly
->isChecked();
315 usercombo
->setEnabled( en
);
316 userbutton
->setEnabled( en
);
320 rstuserbutton
->setEnabled( false );
323 void KDMUsersWidget::slotUserSelected()
325 QString user
= usercombo
->currentText();
327 if (user
!= m_defaultText
&& p
.load( m_userPixDir
+ user
+ ".face.icon" ))
328 rstuserbutton
->setEnabled( !getuid() );
330 p
.load( m_userPixDir
+ ".default.face.icon" );
331 rstuserbutton
->setEnabled( false );
333 userbutton
->setIcon( QPixmap::fromImage( p
.scaled( 48, 48, Qt::KeepAspectRatio
, Qt::SmoothTransformation
) ) );
337 void KDMUsersWidget::changeUserPix( const QString
&pix
)
339 QString
user( usercombo
->currentText() );
340 if (user
== m_defaultText
) {
342 if (KMessageBox::questionYesNo( this, i18n("Save image as default?"),
343 QString(), KStandardGuiItem::save(),
344 KStandardGuiItem::cancel() ) != KMessageBox::Yes
)
350 KMessageBox::sorry( this,
351 i18n("There was an error loading the image\n%1", pix
) );
355 p
= p
.scaled( 48, 48, Qt::KeepAspectRatio
, Qt::SmoothTransformation
);
356 QString userpix
= m_userPixDir
+ user
+ ".face.icon";
357 if (!p
.save( userpix
, "PNG" ))
358 KMessageBox::sorry( this,
359 i18n("There was an error saving the image:\n%1", userpix
) );
364 void KDMUsersWidget::slotUserButtonClicked()
367 dlg
.setCustomLocation( KStandardDirs::installPath( "data" ) + "kdm/pics/users" );
368 dlg
.setup( KIconLoader::NoGroup
, KIconLoader::Any
, false, 48, true, true, false );
369 QString ic
= dlg
.openDialog();
375 void KDMUsersWidget::slotUnsetUserPix()
377 QFile::remove( m_userPixDir
+ usercombo
->currentText() + ".face.icon" );
381 bool KDMUsersWidget::eventFilter( QObject
*, QEvent
*e
)
383 if (e
->type() == QEvent::DragEnter
) {
384 QDragEnterEvent
*ee
= (QDragEnterEvent
*)e
;
385 ee
->setAccepted( K3URLDrag::canDecode( ee
) );
389 if (e
->type() == QEvent::Drop
) {
390 userButtonDropEvent( (QDropEvent
*)e
);
397 KUrl
*decodeImgDrop( QDropEvent
*e
, QWidget
*wdg
);
399 void KDMUsersWidget::userButtonDropEvent( QDropEvent
*e
)
401 KUrl
*url
= decodeImgDrop( e
, this );
404 KIO::NetAccess::download( *url
, pixpath
, parentWidget() );
405 changeUserPix( pixpath
);
406 KIO::NetAccess::removeTempFile( pixpath
);
411 void KDMUsersWidget::save()
413 KConfigGroup configGrp
= config
->group( "X-*-Greeter" );
415 configGrp
.writeEntry( "MinShowUID", leminuid
->text() );
416 configGrp
.writeEntry( "MaxShowUID", lemaxuid
->text() );
418 configGrp
.writeEntry( "UserList", cbshowlist
->isChecked() );
419 configGrp
.writeEntry( "UserCompletion", cbcomplete
->isChecked() );
420 configGrp
.writeEntry( "ShowUsers",
421 cbinverted
->isChecked() ? "NotHidden" : "Selected" );
422 configGrp
.writeEntry( "SortUsers", cbusrsrt
->isChecked() );
424 configGrp
.writeEntry( "HiddenUsers", hiddenUsers
);
425 configGrp
.writeEntry( "SelectedUsers", selectedUsers
);
427 configGrp
.writeEntry( "FaceSource",
428 rbadmonly
->isChecked() ? "AdminOnly" :
429 rbprefadm
->isChecked() ? "PreferAdmin" :
430 rbprefusr
->isChecked() ? "PreferUser" : "UserOnly" );
434 void KDMUsersWidget::updateOptList( Q3ListViewItem
*item
, QStringList
&list
)
438 Q3CheckListItem
*itm
= (Q3CheckListItem
*)item
;
439 int ind
= list
.indexOf( itm
->text() );
442 list
.append( itm
->text() );
445 list
.removeAt( ind
);
449 void KDMUsersWidget::slotUpdateOptIn( Q3ListViewItem
*item
)
451 updateOptList( item
, selectedUsers
);
454 void KDMUsersWidget::slotUpdateOptOut( Q3ListViewItem
*item
)
456 updateOptList( item
, hiddenUsers
);
459 void KDMUsersWidget::slotClearUsers()
464 usercombo
->addItem( m_defaultText
);
467 void KDMUsersWidget::slotAddUsers( const QMap
<QString
,int> &users
)
469 QMap
<QString
,int>::const_iterator it
;
470 for (it
= users
.begin(); it
!= users
.end(); ++it
) {
471 const QString
*name
= &it
.key();
472 (new Q3CheckListItem( optinlv
, *name
, Q3CheckListItem::CheckBox
))->
473 setOn( selectedUsers
.contains( *name
) );
474 (new Q3CheckListItem( optoutlv
, *name
, Q3CheckListItem::CheckBox
))->
475 setOn( hiddenUsers
.contains( *name
) );
476 if ((*name
)[0] != '@')
477 usercombo
->addItem( *name
);
481 usercombo
->model()->sort( 0 );
485 void KDMUsersWidget::slotDelUsers( const QMap
<QString
,int> &users
)
487 QMap
<QString
,int>::const_iterator it
;
488 for (it
= users
.begin(); it
!= users
.end(); ++it
) {
489 const QString
*name
= &it
.key();
490 int idx
= usercombo
->findText( *name
);
492 usercombo
->removeItem( idx
);
493 delete optinlv
->findItem( *name
, 0 );
494 delete optoutlv
->findItem( *name
, 0 );
498 void KDMUsersWidget::load()
502 KConfigGroup configGrp
= config
->group( "X-*-Greeter" );
504 selectedUsers
= configGrp
.readEntry( "SelectedUsers", QStringList() );
505 hiddenUsers
= configGrp
.readEntry( "HiddenUsers", QStringList() );
507 leminuid
->setText( configGrp
.readEntry( "MinShowUID", defminuid
) );
508 lemaxuid
->setText( configGrp
.readEntry( "MaxShowUID", defmaxuid
) );
510 cbshowlist
->setChecked( configGrp
.readEntry( "UserList", true ) );
511 cbcomplete
->setChecked( configGrp
.readEntry( "UserCompletion", false ) );
512 cbinverted
->setChecked( configGrp
.readEntry( "ShowUsers" ) != "Selected" );
513 cbusrsrt
->setChecked( configGrp
.readEntry( "SortUsers", true ) );
515 QString ps
= configGrp
.readEntry( "FaceSource" );
516 if (ps
== QLatin1String("UserOnly"))
517 rbusronly
->setChecked( true );
518 else if (ps
== QLatin1String("PreferUser"))
519 rbprefusr
->setChecked( true );
520 else if (ps
== QLatin1String("PreferAdmin"))
521 rbprefadm
->setChecked( true );
523 rbadmonly
->setChecked( true );
531 void KDMUsersWidget::defaults()
533 leminuid
->setText( defminuid
);
534 lemaxuid
->setText( defmaxuid
);
535 cbshowlist
->setChecked( true );
536 cbcomplete
->setChecked( false );
537 cbinverted
->setChecked( true );
538 cbusrsrt
->setChecked( true );
539 rbadmonly
->setChecked( true );
541 selectedUsers
.clear();
546 void KDMUsersWidget::slotMinMaxChanged()
548 emit
setMinMaxUID( leminuid
->text().toInt(), lemaxuid
->text().toInt() );
551 #include "kdm-users.moc"