Bug 489077

Summary: YaST buttons clickable but may have no immediate effect
Product: [openSUSE] openSUSE 11.2 Reporter: Johannes Meixner <jsmeix>
Component: UsabilityAssignee: Johannes Meixner <jsmeix>
Status: RESOLVED WONTFIX QA Contact: E-mail List <qa-bugs>
Severity: Enhancement    
Priority: P5 - None CC: benderamp, jsrain, scott
Version: Factory   
Target Milestone: ---   
Hardware: All   
OS: SUSE Other   
Whiteboard:
Found By: Development Services Priority:
Business Priority: Blocker: ---
Marketing QA Status: --- IT Deployment: ---

Description Johannes Meixner 2009-03-26 09:52:13 UTC
When a YaST module runs, then in the YaST user interface
the buttons in the dialogs are usually always clickable
(i.e. at any time the user can click a button)
even when at the same time YaST is busy with something else
so that YaST cannot immediately do what the click requests
and in the end the user's click action has no immediate effect.

After YaST had finished why it was busy while the user clicked,
the click event will be actually processed so that the actual
effect of the button click happens delayed.


As far as I know this is just the normal behaviour
of any usual application.

For example when my web-browser (e.g. Firefox)
is busy with downloading a huge amount of data
or when it is busy with starting a helper application
like the Adobe Reade to view a downloaded PDF
there is no immediate effect whe I click a button
in the web-browser's menue.

Therefore the issue in YaST is no bug but nevertheless
the usability feeling is bad so that I filed this
enhancement request for the next openSUSE version
so that we can find out how we could improve the situation.


Here some bug reports where the issue becomes
really annoying for the user:

https://bugzilla.novell.com/show_bug.cgi?id=442173#c4
-------------------------------------------------------------------
clicking the [Abort] button has not the expected effect.
One can click [Abort] but then one must wait all the time
until the GUI has completed its job (i.e. until the list
finally shows up) and afterwards the module is aborted
just when the reason for the abort request is no longer valid.
-------------------------------------------------------------------

bug #442475
-------------------------------------------------------------------
Cancel button ineffective while starting the module
...
If a Yast Application stalls at any point, using the Cancel Button
has NO effect on the Yast Application.
...
IG ANY YAST Module hangs for any reason on completion of settings
you have to wat for it to time out- Abort or Canceled are ignore
-------------------------------------------------------------------

https://bugzilla.novell.com/show_bug.cgi?id=481299#c5
-------------------------------------------------------------------
no matter the cause - the user cannot terminate the process
...
As a conventional user the only option now is
to shut down/restart the PC
-------------------------------------------------------------------


My current personal thoughts:

Usually there is only one single process which is running.

When this single process is busy with something else
it cannot process what a button click requests.

This is a limitation of the core operating system
and we cannot do anything here on the YaST module level
with reasoable effort - this means that it is impossible
regarding the effort when each YaST module author
would have to implement his module as a multi-process
or multi-threaded thingy.

The core operating system can immediately kill any process
at any time by sending appropriate signals to the process.
When a signal was sent to a process, the normal operation
of the process is interrupted by the core operating system
and the process is forced to act on the signal's request.
To kill a process usually first SIGTERM is sent to it
and if this doesn't have an effect SIGKILL is sent.
Those signals can be usually sent by using the window controls
of the window wherein the YaST module runs.
For example I run fvwm2 as window manager and here I have
for each window a dropdown menue with the entries
"Delete" which sends SIGTERM so that where the YaST module
         can respond with a "Really Abort ?" popup dialog
         so that the YaST module can do some cleanup and
         terminate with a clean state
"Destroy" which sends SIGKILL where the YaST module cannot
          respond at all because the core operating system
          will immediately kill the process regardless
          what the state is afterwards

On the other hand the "Cancel/Abort" button in a YaST module
does not send a signal to the YaST module process so that
its normal operation is not interrupted - instead the
"Cancel/Abort" buttons are just normal buttons where
a button click is processed when normal operation allows
to react on button clicks.

This behaviour is intentional because the meaning of the
"Cancel/Abort" button in a YaST module is not to kill it
but to request for normal termination exactly like
the "Quit/Exit" buttons in any other application.

We might discuss if it makes sense to have an additional
"Kill" button in the dialogs of YaST modules which does
send a SIGTERM but do other applications have it?
Or should we also in YaST simply rely on the window manager
(or on [Ctrl]+[C] via keyboard) to send kill signals?


Back to the initial issue in this bug:

As far as I see the root cause of user annoyance is that
for the user it is not clear when a button click has no
immediate effect (because the process is buys otherwise).

I think this can be improved by providing better feedback
to the user what is going on behind the surface.


Some suggestions:

a)
Gay out the buttons when the process cannot respond
to them immendiately?
I think this cannot be implemented correctly because
then also for any stort-time action of the process
the buttons would be grayed off and on all the time
(i.e. results some kind of button flickering).

b)
Change the graphical representation of the button
after it was clicked so that the user can see that
his click request was noticed.
Usually when the process is busy otherwise the cursor
is changed to a busy-cursor so that both the
busy-cursor together with the clicked-button
shows the user what is actually going on.
As far as I see the graphical representation of the button
already changes after it was clicked but the effect is
so minimal that I didn't noticed it up to now.

c)
When when the process is busy with something
where the autor of the YaST module knows
that it can be time-consuming, he would like
to show a busy popup notification.
But here the problem is the word "can" - i.e. the
autor of the YaST module only knows that it may be
time-consuming under whatever circumstances which
are beyond his control (e.g. when re-starting a service).
Therefore he does not want to show a busy popup notification
in any case because when it is not time-consuming
there would be a flickering popup (appear and disappear
almost at the same time).
Currently my personal workaround to avoid the flickering
is that when I show a busy popup where I don't know for sure
it is time-consuming I do an additional "sleep 1 second"
so that the busy popup stays at least for one second.

d)
When when the process is busy with something
where the author of the YaST module does not know
that it can be time-consuming (e.g. in bug #442173),
the underlying YaST system (e.g. the YaST UI) might be able
to provide better feedback than just the busy-cursor?
Comment 1 Martin Schmidkunz 2009-03-27 15:26:36 UTC
> We might discuss if it makes sense to have an additional
> "Kill" button in the dialogs of YaST modules which does
> send a SIGTERM but do other applications have it?

Definitly not. There is enough confusion about this "Cancel"/"Abort" topic.

In some cases the user is already informed if the process takes a longer time (printer detection, scanner detection).
I would be really careful about introducing busy pop ups which might annoy people or confuse people (change graphical representation of a button).
IMHO buttons should be only shown when they are enabled and their action has an effect.
Buttons maybe shown in a disabled state if there is a possibility to enable them in the same dialog.

Sorry, I can't be of more help.
Maybe there is a more technical solution to this topic?
 
Jiri, do you know, whom to address?
Comment 2 Jiri Srain 2009-03-30 08:28:24 UTC
I'm not sure, which is the right solution. Looking at suggestions above:

a) is not a way to go; there are operations which can be interrupted at some point (but not immediately); graying out such button would remove the chance of interrupting such operations. If an operation cannot be interrupted at all, graying out the button is an option, but this can only be done on per-case basis.

b) can be done by the maintainers of individual UIs. If there already is some change in the button representation (I have not noticed), it might be quite simple

c) would work only if you know how much time an operation can take - and does not solve operations which can be interrupted at some points. Flickerking pop-ups are not a prefered way

d) better feedback may be an option, but don't know how to best represent being busy
Comment 3 Johannes Meixner 2009-03-31 08:30:06 UTC
Martin,
you wrote that "buttons should be only shown when they are enabled
and their action has an effect".
This is what I suggested under (a).

What exactly do you mean with "has an effect"?
Is an arbitrary delay o.k. as long as an effect happens at all?
Is then the behaviour in
https://bugzilla.novell.com/show_bug.cgi?id=442173#c4
acceptable?

I explained that currently "buttons are shown even when their action
does not have an effect within a reasonable response time".
Above I wrote "immediately" but actually I meant "within a reasonable
response time".


Martin,
I think "Buttons maybe shown in a disabled state if there is
a possibility to enable them in the same dialog" looks a bit
nonsense.
I wonder why have a button [Foo] disabled and have the
possibility to enable it e.g. after [Bar] was done?
Why not have [Foo] enabled in any case and if clicked
show a feedback popup that e.g. first [Bar] must be done
before [Foo] can have the actually desired effect?
As far as I can imagine at the moment the only case when buttons
are disabled is when there are mutually exclusive things like
one button [paint it black] and another button [paint it white]
(after clicking one it gets disabled and the other one gets enabled)
but for such cases one would usually better use radio buttons.


Jiri,
regarding (a) "operations which can be interrupted":
Obviously an [Interrupt] button (whatever it is actually called)
would not be grayed out if the process can respond to it
(within a reasonable response time).
I meant it the other way round:
If the process is doing an operation which can be interrupted
usually all other buttons except the [Interrupt] button might
be grayed out (as long as all other buttons have no effect).

Is it possible to have a function to gray out all current
active buttons in the current dialog except a list
of interrupt buttons like
  Wizard::DisableAllButtons( [ `id(`one_interrupt_button),
                               `id(`another_interrupt_button) ] )
and a function
  Wizard::RestoreAllButtons()
to restore the buttons to their actual state before DisableAllButtons
was called (i.e. a button which was already disabled before
would stay disabled afterwards)?

Currently the problem is that it is complicated to know which
buttons currently exist and in which state (one would have to
mainain very carefully a list of button IDs and state manually)
whenever the content of a dialog is changed anywhere.
Comment 4 Johannes Meixner 2009-03-31 09:04:16 UTC
I think the issue is not only regarding explicite "button" widgets.

Actually it belongs to any widget where a click has an effect
e.g. radio buttons, combo boxes, items in trees, and even
widgets like `Table( `opt(`notify, `immediate) ...)

Therefore I would like to ask for gereral UI functions like
  UI::DisableAllWidgets( [ `id(`interrupt_button),
                           `id(`notify_immediate_table) ] )
and
  UI::RestoreAllWidgets()

Is it possible with reasonable effort to implement such functions?
Comment 5 Juergen Weigert 2009-03-31 13:35:16 UTC
We have a conceptional confusion here.

I) From a usabiltiy perspective, a button which is enabled, should work. Immediatly and reliably.

II) From a software architecture view, user interaction (which includes buttons) only happens when the program is in its main loop (or whereever the call to XNextEvent() is done).

To implement I) faithfully, the best trick is: 
III) Avoid anything that takes any significant amount of time in the process that controls the UI.

If we cannot guarantee III) our user experience may deteriorate.

Trying to work around with explicit interrupts is the wrong approach. 
Any button should behave as if it were an interrupt. (Having II) and III) is a polling mechanism, giving a user experience very similar to real interupts)

This forces us into seperate processes, one for implementing III) and another one for all potentially longer operation.
Comment 6 Johannes Meixner 2009-03-31 15:02:16 UTC
FYI:
While I talked to Martin I had the idea that one single
status line where status/log messages appear continuously
may help so much here - at least to indicate whether or not
YaST is busy or has hang up.
Just like a browser (at least my Firefox) does it:
At the bottom it shows whatever funny status messages
full of "technical nonsense" but the actual text does
usually not matter at all for the user - all what
matters is whether or not the status line continuously
changes which indicates that the thingy is busy.
And in case of a bug (e.g. a hangup) the status line
is very useful because the user can report what it shows
which may help us to find the root cause much faster.
Comment 7 Martin Schmidkunz 2009-04-01 13:42:16 UTC
Another possible idea would be to introduce a button calles "Status" or "Log" (or something) which could be located next to "Help". The user would be able to get some information about background processes and in case of problems it might help developers/support people to get saner bug reports.
Comment 8 Juergen Weigert 2009-04-01 13:55:12 UTC
Martin, this is a brilliant idea. It is least intrusive for th developers and very helpful to the user. 

This could work like an xtail or tail -f on the logfiles.
The lower part of a stack backtrace could also be helpful.

Not that the exact function calls or log-lines would be meaningful to the user, but as Johannes pointed out earlier, the frequency at which these things change (or not change) is meaningful.
Comment 9 Katarina Machalkova 2009-04-01 14:08:56 UTC
> Therefore I would like to ask for gereral UI functions like
>   UI::DisableAllWidgets( [ `id(`interrupt_button),
>                            `id(`notify_immediate_table) ] )
> and
>   UI::RestoreAllWidgets()
> 
> Is it possible with reasonable effort to implement such functions?

Technically, of course it is possible and not that complicated. But the same can be achieved the same by invoking a busy-popup dialog before the time-consuming operation and closing it afterwards, now doesn't it? The busy pop-up is a modal dialog, thus user can't click around in underlying main dialog just as well ... or am I missing something?
Comment 10 Johannes Meixner 2009-04-01 14:52:28 UTC
Regarding a "Status/Log" button:
We would have here the same problem as the bug describes.

How to make sure that at least this one button
responds immediately in any case?

We would need an interrupt/signal-like solution
at least for this one button together with
an interrupt/signal handler function
to make sure the status/log window
pops up in any case.

This is the reason why I proposed a status line
which exists by default e.g. in each Wizard dialog
where by default simply each line is shown
which goes also into /var/log/YaST2/y2log.
Comment 11 Johannes Meixner 2009-04-01 15:00:28 UTC
Probably better a status line where by default
only YaST2 log-lines with "[YCP]" are shown
to avoid too much useless "technical nonsense"?
Comment 12 Johannes Meixner 2009-04-02 07:54:37 UTC
Katarina, regarding your comment #9:

The technical effect "non-clickable widgets in the dialog"
is the same when there is a pop-up on top of the dialog.

But I think that from the user experience point of view
there is a difference whether the dialog itself adapts
or if pop-up are used, see what Martin wrote in comment #1
"be really careful about introducing busy pop ups".

But in the end the question how it should look like
(disabled widgets directly in the dialog or pop-ups)
can only be answered by the usability experts.
Comment 13 Scott Couston 2009-04-02 17:13:36 UTC
Sorry for the intrusion here - I have been following this issue for a long while. In Software Management we have a 'Skip Refresh' button which acts immediately, why cant we use the same mechanism that the 'skip refresh' button uses and just call it terminate/abort or anything else you like to terminate the task. I am probably being very simplistic, but the 'Skip..." button will always respond in the middle of a task....I am sure technically its not that easy
Comment 14 Johannes Meixner 2009-04-03 09:11:39 UTC
Regarding comment #13:

I find a 'Skip Refresh' button only in
yast2/library/packages/src/PackageCallbacks.ycp
where the code is:
------------------------------------------------------------------
`PushButton (`id(`skip), `opt (`cancelButton), _("Skip Refresh"))
...
symbol ui = (symbol) UI::UserInput();
------------------------------------------------------------------
For documentation see
http://forgeftp.novell.com/yast/doc/SL11.1/tdg/ButtonBox.html
and
http://forgeftp.novell.com/yast/doc/SL11.1/tdg/UserInput.html

This means that whenever a 'Skip Refresh' button is shown,
it explicitely waits directly afterwards for user input.
I.e. whenever a 'Skip Refresh' button is shown, it is never
busy with something else.

In contrast the code which leads to this bug is like:
--------------------------------------------------------------------
`PushButton( `id(`foo), _("Foo") )
...
DoSomethingWhichMayTakeUnexpectedlyMuchTime();
...
symbol ui = (symbol) UI::UserInput();
--------------------------------------------------------------------
As long as it is in the DoSomethingWhichMayTakeUnexpectedlyMuchTime
function, it will not respond to a click on [Foo].

When the author knows it may take much time the code is:
--------------------------------------------------------------------
`PushButton( `id(`foo), _("Foo") )
...
Popup::ShowFeedback( _("Doing Foo. Please wait...") );
DoSomethingWhichIsKnownThatItMayTakeMuchTime();
sleep( 1000 );
Popup::ClearFeedback();
...
symbol ui = (symbol) UI::UserInput();
--------------------------------------------------------------------

When the author knows it takes much time in any case the code is:
--------------------------------------------------------------------
`PushButton( `id(`foo), _("Foo") )
...
Popup::ShowFeedback( _("Doing Foo. Please wait...") );
DoSomethingWhichIsKnownToTakeMuchTime();
Popup::ClearFeedback();
...
symbol ui = (symbol) UI::UserInput();
--------------------------------------------------------------------

But a code like
--------------------------------------------------------------------
`PushButton( `id(`interrupt), _("Interrupt") )
...
DoSomethingWhichIsKnownThatItMayTakeMuchTime();
...
if( `interrupt == (symbol) UI::UserInput() ) exit;
--------------------------------------------------------------------
cannot work because clicking [Interrupt] is ignored
until DoSomethingWhichIsKnownThatItMayTakeMuchTime
had actually finished.
Comment 15 Scott Couston 2009-04-03 19:58:25 UTC
O.K I knew #13 - was probably not going to fly - But case in point this is what I as a user sees as possible. Thanks to Johannes as I do now have a much better technical knowledge of what you are up against with Yast.

Likewise as a user I see a Cancel Button in all Yast Services and it does nothing to stop a misbehaving Yast Service. Because we make poor validity of user field entries Yast Misbehaves quite regularly. So when Yast gets stuck - we all decide to take lunch hour until it times out - AND/OR we have the ability to boot everything - unless its a NFS Server - then we re-boot the whole LAN.

For a normal user - they dont care - they just want whats on the screen to work or respond - This is not a difficult concept Gentlemen!!!!!!!!!!!!!!!!!!!
Comment 16 Scott Couston 2009-04-04 21:28:33 UTC
Another Idea - In KDE4.n Windows Manager if any NON-Yast application either misbehaves or stops running I click a few time on th X to close the Window and a POP-UP Appear immediately asking me if I want to 'Kill Or Keep Running'
If I choose Kill - the app closes without issue and without affecting the stability of the Windows Manager.

Back to ANY Yast Application - I can click on the 'X" to close the window till the cows come home and *NOTHING* happens.

Gentlemen - ALL We work with a machine and code that does what we decide - not the other way arround so there is a way to address this issue and we just might have to become a little more inventive with our code.

Perhaps a little less technical jousting and more thought would help!
Comment 18 Johannes Meixner 2010-03-30 12:21:59 UTC
I am only a YCP user but no low-level YaST programmer.
I cannot fix it.
Comment 20 Johannes Meixner 2010-04-13 07:17:17 UTC
*** Bug 595612 has been marked as a duplicate of this bug. ***