PureMVC Architects Lounge

PureMVC Manifold => MultiCore Version => Topic started by: Ondina on March 19, 2009, 03:43:05



Title: Desktop Citizen Event.CLOSING
Post by: Ondina on March 19, 2009, 03:43:05
Hi Cliff

Desktop Citizen is great. Thanks again for this utility.

I would like to be able to prevent an accidental closing of the WindowedApplication and ask the users if they want to save the data before exiting the application.

In the WindowMediator of the utility there is:
 
stage.nativeWindow.addEventListener( Event.CLOSING, onWindowClosing );

and the method

protected function onWindowClosing( event:Event ):void

I can have a modal dialog  asking the users if they want to exit or not, if I modify it like this:

private var isWorkSaved:Boolean = false;
protected function onWindowClosing( event:Event ):void
{
if( !isWorkSaved )
   {
       event.preventDefault();
       Alert.show( "Would you like to save your work?", "Warning!",Alert.YES | Alert.NO, null, onAlertClose );
        }
}            
protected function onAlertClose( event:CloseEvent ):void
{
   if( event.detail == 1 )
   {
       isWorkSaved = true;
      stage.nativeWindow.visible = false;
      sendNotification( WindowCloseCommand.NAME );
      stage.nativeWindow.close();
   }
}

or I can send a notification inside the  onWindowClosing and have the modal dialog in my application somewhere and then from there sendNotification( WindowCloseCommand.NAME );

I don't know if it's ok with you if  I'd change it like this (copyright license?), but event if it was, isn't there another (better) way to achieve the same when I use DesktopCitizen   And also maybe in other situations I don't want to  have that modal dialog before exiting the application.

Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 21, 2009, 02:49:27
My application deals with sensitive data.
It is very important to prevent an accidental closing of the application when the data is in an unstable ( not saved ) state.
Also it is important to handle  situations where the application is idle for a certain amount of time.

Data loss prevention carries more weight  in my current project than  having the application remember its window size/position .

Since you are not responding I don't know if it's ok with you to have  your utility modified the way  I suggested in my previous post.

I just can assume that you don't want any changes,  therefore  I will have to handle the FlexEvent.IDLE and Event.CLOSING events without using DesktopCitizen.

It's a pity though because I like the DesktopCitizen utility.


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on March 21, 2009, 06:30:07
Ondina,

Please don't make any assumptions based on lack of response within a certain timeframe. I only have so much bandwidth and just now a high volume coupled with an extremely busy work schedule has caused the posts to pile up a bit is all. But as always I am still plowing through it.

As to using a modified version of the utility, feel free to do so, there's no problem at all with that. That's the reason the source is available.

I don't think this is the exact code I would want to fold back into the project, but I think its pretty close. If there was a way to make this an optional hook, that might be nice, but you'd still need to be able to supply it the string. And the developer may not want a standard Alert box for their UI.

Is there no way to move some of this logic and user confirmation outside  of the util? Its an obvious improvement, but I'd like to be able to make it generic enough to be widely reusable.

If you don't have time to figure it out, and this works for your current app, go with your current change, and I'll think about it and eventually get to a solution that can be released.

Thanks for your input in this, I appreciate it.

-=Cliff> 


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 21, 2009, 08:51:18
Ok, no more such assumptions from now on:)

I don't think this is the exact code I would want to fold back into the project, but I think its pretty close. If there was a way to make this an optional hook, that might be nice, but you'd still need to be able to supply it the string. And the developer may not want a standard Alert box for their UI.

Well, I'm not content with that solution either.
No matter if  there is an Alert or another way to get a confirmation from the user  - you are right - it should be optional and outside of the WindowMediator.

As a matter of fact in some situations I don't need the user's confirmation at all, I just need to now that the data is unstable and save it before letting the application close the window.
Also it would be the right time/place ( on Event.CLOSING ) for saving some other application related info/configs in the XML Database together with those window's metrics used by the DesktopCitizen.

I will give it a try and think of a solution despite my “unstable” puremvc-knowledge.
I'll post about it and if it won't be a good approach, it surely  won't be a problem for me to wait until you'll get more time for a really “pure” solution.

Thanks


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 21, 2009, 11:51:37
Look at this:

ApplicationFacade

registerCommand( DesktopCitizenConstants.WINDOW_OPEN, WindowOpenCommand );


ViewPrepCommand


var app:MultiAir = note.getBody() as MultiAir;
facade.registerMediator( new ApplicationMediator( note.getBody() as MultiAir ));
sendNotification( DesktopCitizenConstants.WINDOW_OPEN, app.stage, "1" );

Here I put the flag in the type of the Notification.    


ApplicationMediator

override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
case DesktopCitizenConstants.WINDOW_CLOSING:
if( !isWorkSaved )
{
Alert.show( "Would you like to save your work?", "Warning!",Alert.YES | Alert.NO, null, onAlertClose );
//or just saving
}
break;
....
}

private function onAlertClose( event:CloseEvent ):void
{
if( event.detail == 1 )
{
isWorkSaved = true;
sendNotification( WindowCloseCommand.NAME );
app.stage.nativeWindow.close();
}
}   

DesktopCitizenConstants

public static const WINDOW_CLOSING:String   = "DesktopCitizenWindowClosing";


WindowOpenCommand

var confirm_flag:String = note.getType();
sendNotification(WindowMediator.ENABLE_CONFIRM, confirm_flag);


WindowMediator

public static const ENABLE_CONFIRM:String   = "DesktopCitizenEnableConfirm";

override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
case ENABLE_CONFIRM:
needConfirm = note.getBody() as String;      
break;
...
}

private var needConfirm:String ;
protected function onWindowClosing( event:Event ):void
{
   if(needConfirm=="1"){
   stage.nativeWindow.visible = true;
   event.preventDefault();
   sendNotification( DesktopCitizenConstants.WINDOW_CLOSING);
   }else{
   stage.nativeWindow.visible = false;
       sendNotification( WindowCloseCommand.NAME );
       stage.nativeWindow.close();
}
}   

Not the best solution yet.


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on March 22, 2009, 06:43:04
Looks like you're headed the right direction with this, though. I'd make the value of the confirm flag a constant rather than checking for "1" everywhere, but its shaping up.

-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 22, 2009, 08:00:37
I'd make the value of the confirm flag a constant rather than checking for "1" everywhere, but its shaping up.

Yes, that string is ugly.
I wanted to make it a Boolean, but I've got carried away by the note.type wich is a String... I don't know why I used the note.type anymore.

Actually now I think that there is no need for a flag.
If you want a confirmation before closing you send just  this:

sendNotification(WindowMediator.ENABLE_CONFIRM);
without anything else

and if you don't want to do anything on Event.CLOSING you don't send this notification.

Then in the WindowMediator
private var needConfirm:Boolean=false ;
...
override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
  case ENABLE_CONFIRM:
    needConfirm = true;
  break;
...
}
protected function onWindowClosing( event:Event ):void
{
if(needConfirm){
  event.preventDefault();
  sendNotification( DesktopCitizenConstants.WINDOW_CLOSING);
}else{
  stage.nativeWindow.visible = false;
  sendNotification( WindowCloseCommand.NAME );
  stage.nativeWindow.close();
}
}   

Would you agree?
I'll think about a constant though.

----

Now that I'm playing around with the DesktopCitizen I can see again a behavior that I already noticed  before but  didn't care much about it:
If you start the application and just move the window around, then close the window and open it  again only the size is correct, not the position.
It  works as it should, only if you first move the window and then resize it.

Both events:

stage.nativeWindow.addEventListener( NativeWindowBoundsEvent.RESIZE, onResize );
stage.nativeWindow.addEventListener( NativeWindowBoundsEvent.MOVE, onResize );

are handled in
protected function onResize( event:Event ):void

Tracing the rect.x and rect.y for both event types shows that RESIZE overwrites the x and y due to  MOVE with different values.
In DesktopCitizen.xml the values of x and y change only after RESIZE.

So if you only have
stage.nativeWindow.addEventListener( NativeWindowBoundsEvent.MOVE, onResize );
and look at the  DesktopCitizen.xml  the values of  x and y don't change at all after closing.
I know, I'm not explaining it well...

Maybe a nativewindow bug ?


Thank you.

EDIT:
I did this:

protected function onResize( event:NativeWindowBoundsEvent ):void
{
   if ( windowMetricsProxy.displayState == NativeWindowDisplayState.NORMAL )
   windowMetricsProxy.bounds =  event.afterBounds ;
  sendNotification( WINDOW_SHOW );
}

and now it works on MOVE too.


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on March 24, 2009, 07:44:12
Excellent catch on the move without resize bug!

And on the constant for the enable flag, I see no reason for it with your new scheme. That's far cleaner. When you're finished, if you could send me a patch, I'll apply it to the codebase and rev the utility. I'm using it on a current project so I can try it out right away. I'll test this buggy behavior on the move without resize tonight.

Thanks,
-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 24, 2009, 10:26:02
Thank you Cliff  for your approval  :)

I don't know how to send a patch or what you meant by it, but I'll attach the application that contains all the changes.

The DesktopCitizenTest.zip  file is 32k.

The library files for DesktopCitizen are under src.org

You can play around with it and see what could be done better.

I don't know if you can  see any use for the Event.USER_IDLE and Event.USER_PRESENT.
I need them in my current project, but I can handle that without DesktopCitizen in case you don't want them there.

Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on March 24, 2009, 04:48:03
In FlexBuilder with the svn plugin if you have something checked out of a repository you don't have write access to, you can create a 'patch' which essentially is the changes you would otherwise check in. Most large software projects work on this principle, with patches being submitted by users, then a committer applies the patch to their checked out copy, test it and if approved, commits the changes. No worries though, you can just zip it all up and mail it to me. I do most of my forum maintenance on my Blackberry catch as catch can, so unless an important submission shows up in my email where I can mark it for follow up, it runs the risk of falling between the cracks.

For these user events, I'm not sure they conceptually fit with the utility reallyunless you see a good reason for it.

Thanks,
-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 25, 2009, 12:04:34
In FlexBuilder with the svn plugin if you have something checked out of a repository you don't have write access to, you can create a 'patch' which essentially is the changes you would otherwise check in. Most large software projects work on this principle ...

Aha, ok. 
I'm not part of a large software team, so I've  never had to use it until now. I'll take a look at it.

You can just zip it all up and mail it to me.

The zip file was in the attachment of my previous post, but I'll mail it to you if you can't download it from there.

Thank you for  your time and your assistance :)

Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: Jason MacDonald on March 25, 2009, 06:07:54
I'm not part of a large software team, so I've  never had to use it until now. I'll take a look at it.

Even as a single developer, I use SVN just so I have the ability to roll back changes. I often go down a road of development that I later decide isn't working out. And being able to roll back my changes to the point where I started is a blessing. :)


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 25, 2009, 06:19:11
Yeah, good point Jason. I'll follow your advice and make SVN into a habit.
Thank you:)


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 27, 2009, 08:20:50
Hey Cliff

DesktopCitizen is addictive.

Now I'm wasting spending my time with a behavior I would like to catch:
I want to relocate the window in case the user moves it off screen.

Something like this:

private var mainScreen:Screen = Screen.mainScreen;
private var screenBounds:Rectangle = mainScreen.bounds;
protected function onResize( event:NativeWindowBoundsEvent ):void
{
   var windowRectangle:Rectangle=event.afterBounds;
   stage.nativeWindow.bounds = windowRectangle;

   if(event.afterBounds.left<screenBounds.left ){
      windowRectangle.offset( -event.afterBounds.left, 0 );
   }
   if( event.afterBounds.top<screenBounds.top){
      windowRectangle.offset( 0, -event.afterBounds.top );
   }
...



Have you got the time to look at the files I attached in a previous post and also sent per email? Just curious, nothing more.

Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on March 27, 2009, 10:19:38
Hi Ondina,

Unfortunately, I'm onsite with a client and haven't had much time to do more than answer forum questions from my phone when a second arises, and evening bandwidth has dropped to zero since I'm preparing a presentation and a product against a late april deadline. I do anticipate getting into it, sinxe the product uses the util, but I'm flying home and back this weekend. Probably next week sometime.

On the moving offscreen part, be careful, the user may have two monitors. At home, I do, and the utility is able to remember which screen. But it would be quite nice to be able to detect if the other monitor is off, as it is when I disconnect my laptop and take it on the road. I'm not sure if AIR knows how many screens you have. It just looks like one big desktop. So if you do the math and determine the window is off the current desktop bounds, yes, itd be nice to perhaps center it within the current bounds.

-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on March 28, 2009, 03:14:22
When I have to work on a web project I also need a second monitor.
After reading your answer I tried it out with 2 monitors.

The key for detecting the monitors is :

Screen.getScreensForRectangle()

"Returns the (possibly empty) set of Screens that intersect the provided rectangle."


protected function onResize( event:NativeWindowBoundsEvent ):void
{
      var screens:Array = Screen.getScreensForRectangle( event.target.bounds );   
      var screen:Screen;
      if( screens.length == 1 ) { 
            Alert.show("FIRST monitor");
            screen = screens[0];                                             
    }else if( screens.length == 2 ) { 
         Alert.show("SECOND monitor");                                     
         screen = screens[1];                                             
   }   
...
}

So far so good, but I'm not sure how to use it yet.

Maybe there should be an additional attribute “screens” in that DesktopCitizen.xml?

I'll let you know as soon as I find a solution.

--

Apropos your presentation at FITC Toronto, will it be recorded?

Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on April 01, 2009, 04:00:39
Thanks for researching this Ondina. I think it might be useful for us to have at least an automatic function to bring the window back onto screen 1 if screen 2 isn't there and the app is fully within the bounds of screen 2. But if its only partially offscreen, we may not want to. Imagine you've resized the app so it takes up all of screen 2 and only extends partway onto screen 1 and that's how you like it.

I do hope they'll record and make available the FITC talk, but I'm not sure. I'll see.

-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on April 02, 2009, 04:31:06
Hi Cliff

Here is how I solved the positioning of the window when using 2 monitors.

:
  private var windowRectangle:Rectangle;
  private var isOnSecondMonitor:Boolean = false;

   protected function onResize( event:NativeWindowBoundsEvent ):void
   {
windowRectangle = event.afterBounds;
detectScreens();
if ( windowMetricsProxy.displayState == NativeWindowDisplayState.NORMAL ){
windowMetricsProxy.bounds = windowRectangle;
sendNotification( WINDOW_SHOW );
}
  }
  protected function detectScreens():void
  {
var screens:Array = Screen.getScreensForRectangle(windowRectangle);   
var screen:Screen;
var screenIndex:uint = screens.length - 1;
if( screens.length == 1 ){ 
   screen = screens[screenIndex]; 
if( screen.bounds.x > 0 ){
isOnSecondMonitor = true;
trace("window is on SECOND monitor");   
}else{
isOnSecondMonitor = false; 
trace("window is on FIRST monitor");
}
keepOnScreen(screen.bounds);
}                                                                    
else{ 
screen = screens[screenIndex];       
trace("window is on both monitors ( do nothing )");
}  
  }
  protected function keepOnScreen( screen:Rectangle ):void
  {
var rightMargin:Number = screen.x + screen.width + 1;
var bottomFactor:Number = 84;
if( !isOnSecondMonitor ){
if( windowRectangle.left < 0 ){
windowRectangle.offset( -windowRectangle.left, 0 );
}
}
if( isOnSecondMonitor ){
  if( windowRectangle.right > rightMargin ){
windowRectangle.offset( -(windowRectangle.right-rightMargin), 0 );
}
}
if( windowRectangle.top < 0 ){
windowRectangle.offset( 0, -windowRectangle.top );
}
if( windowRectangle.bottom > screen.bottom ){
windowRectangle.offset( 0, -(windowRectangle.bottom-screen.bottom-bottomFactor) );
}
  }

That's far from being perfect, but it's a start.

I couldn't test it on more than 2 monitors, but I think little changes to the code would make it work on multiple monitors as well.

The bottomFactor, you'll see in my code, is an ugly thing. I didn't know how to get the height of the (MS Windows) taskbar for the first monitor. The second monitor doesn't have a taskbar.

There was this post
http://forums.puremvc.org/index.php?topic=1145.0 (http://forums.puremvc.org/index.php?topic=1145.0)
about DesktopCitizen and a Window object opening from the main mxml.

That made me think about how would DesktopCitizen remember the position of that Window object. I don't know the answer yet, but I think it's worth finding a solution, since there might be a need for having Windows objects opening together with the WindowedApplication.

Enjoy the Spring,
Ondina


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on April 02, 2009, 06:34:52
I don't know if I was the only one in the world who was wondering where the Mozilla Firefox has gone while trying to launch it – a (long) time ago.
It appeared on the taskbar, but it wouldn't launch.
Anything else ( even IE ) worked fine.

I was sure it was a bug or who knows maybe a virus.
Or maybe MS Windows wouldn't like Firefox anymore.
Lots of stupid scenarios, you know...

It took me a while to realize that I had Firefox on my second monitor the other day, and now the monitor was closed. My Firefox was there!

So I thought, if that would happen to others too while opening an AIR Application managed by the DesktopCitizen, it would be nice to tell the user where the window is, just in case there are multiple monitors available.

So when the application is on the second monitor I send a notification to the ApplicationMediator, that will change the title of the WindowedApplication:
“MyTitle -> Second Monitor”, and that's what the user would see on the taskbar.

In the code from my previous post I would do that:
if( screen.bounds.x > 0 ){
  isOnSecondMonitor = true;
  trace("window is on SECOND monitor");
  sendNotification(SET_APP_TITLE, "Second Monitor");
}
and in the ApplicationMediator
case WindowMediator.SET_APP_TITLE:
   app.title = " ->"+note.getBody() as String;
break;


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on April 02, 2009, 07:32:00
Maybe shorten that to 'MyApp 2nd Monitor" since taskbar button text gets shortened.

You're right, some apps are smart enough to move themselves to the first monitor to avoid this problem of 'where is my app' and some don't. You have to right click on the taskbar button, select 'Move' and use arrow keys to move it onscreen. I prefer the intelligent behavior of repositioning it if the other monitor isn't there, because clearly if I launched the app and don't have the second monitor, I'll be moving it. Or, as you were, be confused and think somethings wrong.

Also, on the bottomFactor, don't assume the taskbar is on the bottom (or top of the screen.

I have an 'L' shaped desk with my extra monitor in the corner, and laptops on either side. I have a Belkin Flip that let's both laptops share the monitor. The laptop on the left has the taskbar vertically aligned on the left side of its screen, and the extra monitor extends the desktop to. The right.

The right side laptop mirrors this with its taskbar vertically aligned on the right side of Its screen with the extra monitor extending the desktop to the left.

An edge case to be sure, but then I'm an edgy bloke. :)

-=Cliff> 


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on April 02, 2009, 08:36:41
some apps are smart enough to move themselves to the first monitor

Those apps might have a way to communicate with the OS ??
Or do you know how to let an AIR app be aware of the absence of the others monitors?

I couldn't find a way of knowing if the other monitor is there by the time the application starts.

I only know that the application “is” on the second monitor, even if it's Nirvana:)

That's why I tried to change the application's title as a warning for the user.

Also, on the bottomFactor, don't assume the taskbar is on the bottom (or top of the screen.

Hmm, you are right!
That might complicate the repositioning on the right and on the left as well, as I did it in my code.

The right side laptop mirrors this with its taskbar vertically aligned on the right side of Its screen with the extra monitor extending the desktop to the left.

Are the extra monitors considered to be the 2nd and 3rd ... monitors, or I should ask where does  DesktopCitizen open  the app the first time (default).

Well well, so many scenarios... why did I start playing around with DesktopCitizen? :)

P.S. Maybe  changing the SystemTrayIcon (adding an item “Back to 1st Screen” or something) would be an acceptable compromise ?


Title: Re: Desktop Citizen Event.CLOSING
Post by: puremvc on April 02, 2009, 04:46:12
Is the measured stage size different if the second monitor is off? I was suprised when it was able to cope with the second screen when I wrote it. It appeared that the stage rectangle was simply large enough to encompas them both without knowing that there was another monitor per se. So I'm imagining that when my second monitor is gone, the stage bounds shrink to the size of the first screen. If so it should be as easy as detecting if the window lies within the stage bounds or not.

-=Cliff>


Title: Re: Desktop Citizen Event.CLOSING
Post by: Ondina on April 03, 2009, 03:32:01
So I'm imagining that when my second monitor is gone, the stage bounds shrink to the size of the first screen.

That doesn't happen.

Look at my tracings. By the way my second monitor is smaller than the main monitor.

App starts on the first Monitor:

screens.length : 1
window is on FIRST monitor
screen.bounds (x=0, y=0, w=1280, h=1024)
event.afterBounds (x=293, y=192, w=719, h=619)
stage.nativeWindow.bounds (x=293, y=192, w=719, h=619)

I move the window, so it intersects the second monitor:

screens.length : 2
window is on both monitors ( do nothing )
screen.bounds (x=1280, y=0, w=1024, h=768)
event.afterBounds (x=904, y=125, w=719, h=619)
stage.nativeWindow.bounds (x=293, y=192, w=719, h=619)

Now the window is on the second monitor:

screens.length : 1
window is  on SECOND monitor
screen.bounds (x=1280, y=0, w=1024, h=768)
event.afterBounds (x=1465, y=57, w=719, h=619)
stage.nativeWindow.bounds (x=904, y=125, w=719, h=619)

Here the window is minimized:

screens.length : 0
window is minimized

When you just move the App, the stage.nativeWindow.bounds change their x and y values, not the size. Only the screen.bounds reflect the monitor's size.
And only the event.afterBounds rectangle shows the real position of the Application on the screen.

So even if you unplug your 2nd monitor, the above tracings will be the same.
If the App was on the second monitor last time you opened it, it will still be there when you start your App again, even if your second monitor is gone. If you then activate your second monitor, the App is still there (2nd Monitor), waiting for you :)

I don't know what would happen, if the MS Windows settings for the Dual Display changed, meaning that you'd have only one monitor defined in the settings and you tried the above. I don't want to try this on my computer, but I think it would be the same:
you would know that your App is NOT on the main monitor - eventually as in my code that it is on the second monitor, but you wouldn't know if other monitors are there or not.

So until you'll find an “edgy”   ;)  solution, I will just notify the user about the Application being on other monitors through the  App Title and add an item to the SystemTrayIcon, that will let them take the App back on the main monitor.