Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shoes.app contents unavailable outside its scope #44

Closed
IanTrudel opened this issue Jan 26, 2015 · 16 comments
Closed

Shoes.app contents unavailable outside its scope #44

IanTrudel opened this issue Jan 26, 2015 · 16 comments

Comments

@IanTrudel
Copy link
Collaborator

The contents of any given Shoes.app is unavailable outside its scope, regardless of the manner it is referenced (variable, Shoes.APPS, etc). For example, on Windows, Shoes.app(3) can refer to its own contents using Shoes.APPS[1] but not access contents to other app listed in Shoes.APPS. On MacOS X, Shoes.app(2) can access to contents of Shoes.app(1) whereas Shoes.app(3) cannot. Other tests have proven that it is possible to add slots and elements to an existing Shoes app.

This issue seriously limits the ability to interact amongst Shoes.APPS but also when using IRB for fast prototyping or modifying a running Shoes app. _BE AWARE_ The Shoes Console output is different on Windows than MacOS X (and likely Linux too); platform specific implementation may cause the slight differences.

The Shoes script is available below after the console outputs.

SHOES CONSOLE ON WINDOWS

Info in <unknown> line 0 | 2015-01-26 10:35:38 -0500
contents: [(Shoes::Types::Stack), (Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.APPS[0].contents: [(Shoes::Types::Stack), (Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:35:38 -0500
stack contents [(Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.app(3)>>Shoes.APPS[1].contents: [(Shoes::Types::Flow)]

Error in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.app(2)>>Shoes.APPS:
undefined method `dump' for nil:NilClass
C:/Program Files (x86)/Shoes/lib/shoes/inspect.rb:126:in `inspect'
contents_not_available.rb:22:in `inspect'
contents_not_available.rb:22:in `block in <main>'
eval:1:in `instance_eval'
eval:1:in `block in <main>'
-e:1:in `call'

Error in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.app(2)>>Shoes.APPS[0]:
undefined method `contents' for (Shoes::Types::App "Shoes")
contents_not_available.rb:28:in `method_missing'
contents_not_available.rb:28:in `block in <main>'
eval:1:in `instance_eval'
eval:1:in `block in <main>'
-e:1:in `call'

Info in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.app(2)>>Shoes.APPS: [(Shoes::Types::App "Shoes"), (Shoes::Types::App "Shoes"), (Shoes::Types::App "Shoes"), (Shoes::Types::App "Shoes")]

Error in <unknown> line 0 | 2015-01-26 10:35:38 -0500
Shoes.app(2)>>Shoes.APPS[0]:
undefined method `contents' for (Shoes::Types::App "Shoes")
contents_not_available.rb:10:in `method_missing'
contents_not_available.rb:10:in `block (2 levels) in <main>'
eval:1:in `instance_eval'
eval:1:in `block in <main>'
-e:1:in `call'

SHOES CONSOLE ON MACOS X

Info in <unknown> line 0 | 2015-01-26 10:43:12 -0500
contents: [(Shoes::Types::Stack), (Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:43:12 -0500
Shoes.APPS[0].contents: [(Shoes::Types::Stack), (Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:43:12 -0500
stack contents [(Shoes::Types::Para)]

Info in <unknown> line 0 | 2015-01-26 10:43:12 -0500
Shoes.app(2)>>Shoes.APPS: [(Shoes::Types::App "Shoes"), (Shoes::Types::App "Shoes")]

Info in <unknown> line 0 | 2015-01-26 10:43:12 -0500
Shoes.app(2)>>Shoes.APPS[0].contents: [(Shoes::Types::Stack), (Shoes::Types::Para)]

Error in <unknown> line 0 | 2015-01-26 10:43:12 -0500
undefined method `contents' for (Shoes::Types::App "Shoes")
contents_not_available.rb:20:in `method_missing'
contents_not_available.rb:20:in `block in <main>'
eval:1:in `instance_eval'
eval:1:in `block in <main>'
contents_not_available.rb:18:in `call'
contents_not_available.rb:18:in `app'
contents_not_available.rb:18:in `<main>'
/Users/ian/Downloads/Shoes.app/Contents/MacOS/lib/shoes.rb:470:in `eval'
/Users/ian/Downloads/Shoes.app/Contents/MacOS/lib/shoes.rb:470:in `visit'
eval:1:in `<main>'

SHOES SCRIPT

@app = Shoes.app {
   s = stack { para "stack paragraph" }
   para "paragraph"
   info "contents: #{contents}"
   info "Shoes.APPS[0].contents: #{Shoes.APPS[0].contents}"
   info "stack contents #{s.contents}"
   Shoes.app {
      info "Shoes.app(2)>>Shoes.APPS: #{Shoes.APPS}"
      begin
         info "Shoes.app(2)>>Shoes.APPS[0].contents: #{Shoes.APPS[0].contents}"
      rescue Exception => e
         msg = e.backtrace.join("\n")
         error "Shoes.app(2)>>Shoes.APPS[0]:\n#{e.message}\n#{msg}"
      end
   }
}

Shoes.app {
   flow { para "flow paragraph" }
   info "Shoes.app(3)>>Shoes.APPS[1].contents: #{Shoes.APPS[1].contents}"
   begin
      info "Shoes.app(3)>>Shoes.APPS: #{Shoes.APPS}"
   rescue Exception => e
      msg = e.backtrace.join("\n")
      error "Shoes.app(2)>>Shoes.APPS:\n#{e.message}\n#{msg}"
   end
   begin
      info "Shoes.app(3)>>Shoes.APPS[0].contents: #{Shoes.APPS[0].contents}"
   rescue Exception => e
      msg = e.backtrace.join("\n")
      error "Shoes.app(2)>>Shoes.APPS[0]:\n#{e.message}\n#{msg}"
   end
}

Shoes.show_log
@ccoupe
Copy link

ccoupe commented Jan 28, 2015

As currently written, there is no way in Shoes to reliably manage other Apps/Wiindows. It is confounded by the other (mostly unwritten about) url/visit style which does allow modal dialog like control . My sample ytm.rb at http://walkabout.mvmanila.com/public/share/ytm.rb shows this style of Shoes programming (you'll need http://walkabout.mvmanila.com/public/share/ytmsec.csv
ytmsec.csv to run the code, you might need the icon too).

An IRB facility does need that kind of modal control and inspection into Shoes.apps (kind of like gdb has to control its apps and threads and set values in the target. Perhaps your irb could be written in that style and if you need some api's into Shoes internals we can write them. Just a suggestion.

@IanTrudel
Copy link
Collaborator Author

The code seems to be accommodating on the surface but clearly not working to manage multiple Shoes apps. One oddities in https://github.com/Shoes3/shoes3/blob/master/shoes/app.c#L80 is the usage of rb_ary_dup in shoes_apps_get, which seems like a very bad idea. Also, shoes_app_new indeed pushes the app into the array.

It might be necessary to gdb this one because even though the object is listed, some of its methods are not available. It is possible to add slots and elements to a window but not read it without previously defining local variables (such as in my IRB turtorial). I did test without rb_ary_dup on Loose Shoes but without success. Accessing contents of Shoes.APPS[0] from a second window still generates an undefined method:

Error in <unknown> line 0 | 2015-01-28 11:18:41 -0500
undefined method `contents' for (Shoes::Types::App "Shoes")
contents_simple.rb:12:in `method_missing'
contents_simple.rb:12:in `block in <main>'
eval:1:in `instance_eval'
eval:1:in `block in <main>'
-e:1:in `call'

You very well understand what I ought to do with IRB. Adding a special API will certainly work but it doesn't fix the core problem. Proper support within Shoes would most certainly make IRB an amazingly useful tool but also it would allow multiple windows applications to exist, such as what once was Gimp.

@IanTrudel
Copy link
Collaborator Author

Investigation reveals that Shoes should properly support multiple apps. The reason the contents is not available outside its scope is that Shoes::Types::App is not related to Shoes::Types::Canvas in any way.

It is however unclear why the following code snippet will display the contents. Canvas may be mixin in some way. Using Shoes IRB will not display the contents (directly, using put or info). Also, info top within a Shoes.app crashes Shoes on Windows — top would be a Canvas method.

Shoes.app {
  para "Check Shoes console (alt-/)"
  info self.class
  info contents
  info (self.class.instance_methods - Object.class.instance_methods).sort
}

REFERENCES
https://github.com/Shoes3/shoes3/blob/master/shoes/ruby.c#L4419 (Shoes::Types::App)
https://github.com/Shoes3/shoes3/blob/master/shoes/ruby.c#L4370 (Shoes::Types::Canvas)

@ccoupe
Copy link

ccoupe commented Feb 24, 2015

The clues are in ruby.c lines:4364 through 4394.

@IanTrudel
Copy link
Collaborator Author

The clues are in ruby.c lines:4364 through 4394.

Funny enough, on line 4392 there is cShoes = rb_define_class("Shoes", cCanvas);. Shoes is a Canvas and Shoes::Types::App is not. How would you fix this?

I did try earlier to set the parent class of cApp to cCanvas but Shoes crashed (segfault, shoes.rb:152) before displaying the splash screen. The two following wouldn't work.

  cApp = rb_define_class_under(cTypes, "App", cCanvas);
  cApp = rb_define_class("App", cCanvas);
  rb_include_module(cApp, cTypes);
  rb_define_alloc_func(cApp, shoes_app_alloc);

@ccoupe
Copy link

ccoupe commented Feb 24, 2015

For a maintenance release, I wouldn't fix it ;^) This code and the manipulation of self is what makes Shoes unique (and confusing). As you've discovered, modifying stuff here almost always results in unintended behavior.

@IanTrudel
Copy link
Collaborator Author

Come on, we have already triumphed over a few "couldn't, wouldn't fix". :)

@ccoupe
Copy link

ccoupe commented Feb 25, 2015

compares the lists produced by

Shoes.app {
  info Shoes::included_modules
  info Shoes::instance_methods
  info Shoes::Types::included_modules
  info Shoes::Types::instance_methods
  info Shoes::Types::App::instance_methods
  info self.class
}

@IanTrudel
Copy link
Collaborator Author

Updated your code for clarity in the output:

Shoes.app {
  info Shoes::included_modules
  info (Shoes::instance_methods - Object.instance_methods).sort
  info Shoes::Types::included_modules
  info (Shoes::Types::instance_methods - Object.instance_methods).sort
  info (Shoes::Types::App::instance_methods - Object.instance_methods).sort
  info self.class
}

@ccoupe
Copy link

ccoupe commented Sep 12, 2015

I can guess _why rb_ary_dup was used. If Window-1 gets a list of APPS[] there is no current way that Windows-1 knows that Windows-2 spawned a half dozen windows and closed 3 of them. Or that Windows-3 (IRB for example) is poking inside Windows-1 and closed a fourth windows from there.

APPS is only an [array] of Apps/Windows/Alerts it knows about at the time it is queried. For introspection we really need a dynamic tree structure of windows at that level in the tree and ways to move up, down, left and right in the tree node. Not so simple for anyone and very not backwards compatible but that's OK, IMO.

We have a data structure problem with APPS[] and we will need some hashes or nested arrays or both. For example APPS[] could return the( flat) array of all the windows it knows about at the time. as it does now for backwards compatibility. Shoes.AppTree (.e.g) could return a dynamic list of list or list of hashes or ....

Window titles cannot be use as a hash key since a scrip(s)t can change those. A global C var may already exist for counting windows that can be use to create keys or we can create one. I'm only exploring the idea. Might be a hell of lot of 'C' code to modify and app issues will bite you in the ass.

Thoughts?

@ccoupe
Copy link

ccoupe commented Sep 15, 2015

Before I start modifying anything, it's good to explore how this corner of Shoes works and since I'm not particularly knowledgeable with meta programming and the Shoes core class arrangement I wrote a program to explore it. https://gist.github.com/ccoupe/95e095cbb749ba7c5006 It's Shoes so all the windows stack on top so you have to spread them out manually. Ugly.

It demonstrates that one can manipulate other Shoes windows from Shoes. Not easily because you have to instance_eval to get the binding correct and string escaping can be horrendous. However for this bug report, you can do it.

To use eval(string, TOPLEVEL_BiNDING) as IRB (and others) like to do is a problem unless Shoes/irb can switch (+save/restore) the binding. It could do that I think but it would be ugly, hackish, and prone to breakage. But I'm not very clever so perhaps someone else can do better.

@ccoupe ccoupe added the Low label Sep 15, 2015
@ccoupe ccoupe added this to the 3.2.25 milestone Sep 20, 2015
@ccoupe ccoupe self-assigned this Sep 20, 2015
@ccoupe
Copy link

ccoupe commented Sep 20, 2015

Turns out there is an existing App.slot method documented (by @passenger94 ?) " as
no idea what this does". app.slot.contents does appear to return canvas.contents. It's at app.c:161

I would prefer the name to be 'slots' but for compatibility reasons I will dup the tiny bit of code and rename it to slots and document slots.

And I may have found a good test case for when Shoes doesn't alway default to flow layout - a bug that has bothered me forever.

@backorder' original test script has been modified to add a start block because not everything is realized at the instantly and use slot.contents.

@app = Shoes.app {
   s = stack { para "stack paragraph" }
   para "paragraph"
   info "contents: #{contents}"
   info "Shoes.APPS[0].contents: #{Shoes.APPS[0].slot.contents}"
   info "stack contents #{s.contents}"
   Shoes.app {
      info "Shoes.app(2)>>Shoes.APPS: #{Shoes.APPS}"
      begin
         info "Shoes.app(2)>>Shoes.APPS[0].contents: #{Shoes.APPS[0].slot.contents}"
      rescue Exception => e
         msg = e.backtrace.join("\n")
         error "Shoes.app(2)>>Shoes.APPS[0]:\n#{e.message}\n#{msg}"
      end
   }
}

Shoes.app {
   flow { para "flow paragraph" }
   info "Shoes.app(3)>>Shoes.APPS[1].contents: #{Shoes.APPS[1].slot.contents}"
   start do
     begin
      info "Shoes.app(3)>>Shoes.APPS: #{Shoes.APPS.inspect}"
     rescue Exception => e
      msg = e.backtrace.join("\n")
      error "Shoes.app(2)>>Shoes.APPS:\n#{e.message}\n#{msg}"
     end
     begin
      info "Shoes.app(3)>>Shoes.APPS[0].contents: #{Shoes.APPS[0].slot.contents}"
     rescue Exception => e
      msg = e.backtrace.join("\n")
      error "Shoes.app(2)>>Shoes.APPS[0]:\n#{e.message}\n#{msg}"
     end
   end
}

Shoes.show_log

Also you guys should join the new mailing list even if you just want to lurk.

ccoupe pushed a commit that referenced this issue Sep 20, 2015
* add app.slots
* update manual (search 'app.slots'). Includes 'run this' example
* needs a wiki post to describe what's really going on.
@IanTrudel
Copy link
Collaborator Author

Seems like it is working as it should. It does feel a bit overkill to have to used a slot method rather than just APPS. Changing APPS to reflect slot behaviour might need much work. This technique using slot on APPS should be documented in the manual if no other steps are taken on this issue here. Excellent work, @ccoupe!

@ccoupe
Copy link

ccoupe commented Sep 20, 2015

It's definitely requires a long wiki article about how to poke around inside the DSL. The start() method was also critical in the above example because that's how things work. The manual has been updated for 3.2.25

@ccoupe
Copy link

ccoupe commented Sep 21, 2015

Folks might be interested in the wiki article: https://github.com/Shoes3/shoes3/wiki/Poking-in-Shoes.app which adds a button in another window using the methods discovered above.

@IanTrudel
Copy link
Collaborator Author

This is a very good wiki article. Shoes is getting gradually demystified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants