qt_connect 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.LIB.txt +510 -0
- data/README.md +257 -0
- data/Rakefile +72 -0
- data/examples/classexplorer/classexplorer.rb +828 -0
- data/examples/classexplorer/menus.rb +1 -0
- data/examples/classexplorer/menus/helpmenu.rb +73 -0
- data/examples/classexplorer/packagetree.rb +91 -0
- data/examples/classexplorer/qt_extensions.rb +2 -0
- data/examples/classexplorer/qt_extensions/htmldelegate.rb +40 -0
- data/examples/classexplorer/qt_extensions/line_edit.rb +65 -0
- data/examples/deform/Deform.html +23 -0
- data/examples/deform/Thumbs.db +0 -0
- data/examples/deform/arthurframe.rb +273 -0
- data/examples/deform/deform-demo.png +0 -0
- data/examples/deform/deform.rb +502 -0
- data/examples/deform/qt-logo.png +0 -0
- data/examples/qtmapzoom/qtmapzoom.rb +340 -0
- data/java/SignalActions.java +13 -0
- data/lib/Qt.rb +10 -0
- data/lib/qt_connect.rb +12 -0
- data/lib/qt_connect/qt_compat.rb +700 -0
- data/lib/qt_connect/qt_jbindings.rb +657 -0
- data/lib/qt_connect/qt_sugar.rb +277 -0
- data/lib/qt_connect/version.rb +3 -0
- data/lib/qt_jambi.rb +8 -0
- data/lib/signalactions.jar +0 -0
- metadata +92 -0
data/README.md
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
qt\_connect
|
2
|
+
==========
|
3
|
+
|
4
|
+
This project provides a Qt programming API for JRuby with a high degree of compatibility
|
5
|
+
with the existing Qt interface for Ruby. The API is implemented using Qt Jambi, the Java version
|
6
|
+
of the Qt framework. The interface consists of two software components: the actual Ruby
|
7
|
+
bindings (qt\_jambi) and a compatibility layer (qt\_compat) which makes it easier to run
|
8
|
+
existing Qt Ruby code using JRuby.
|
9
|
+
A third component (qt\_sugar) introduces the Qt Jambi signals interface to QtRuby. With this interface
|
10
|
+
there is no need to use C++ signatures or slots on the application level.
|
11
|
+
The three components are packaged in a single, pure-Ruby gem package 'qt\_connect'. The same gem
|
12
|
+
can be installed on Ruby and JRuby installations.
|
13
|
+
|
14
|
+
### prerequisites and usage
|
15
|
+
**Ruby**
|
16
|
+
>*prerequisite:* qtbindings Ruby gem package
|
17
|
+
>>download from: <http://github.com/ryanmelt/qtbindings>
|
18
|
+
>>installation: follow qtbindings instructions
|
19
|
+
|
20
|
+
>`require 'qt_connect'` to use Qt Jambi Signals interface
|
21
|
+
|
22
|
+
**JRuby**
|
23
|
+
>*prerequisite:* qtjambi Java library
|
24
|
+
>>download from: <http://qt-jambi.org>
|
25
|
+
>>installation: install two jar-files on CLASSPATH (e.g. C:/jruby-1.6.7/lib or /usr/lib/jruby/lib)
|
26
|
+
>>qtjambi-`<version>`.jar (e.g. qtjambi-4.7.1.jar)
|
27
|
+
>>qtjambi-`<platform>`-`<version>`.jar (e.g. qtjambi-win32-msvc2008-4.7.1.jar)
|
28
|
+
|
29
|
+
>`require 'qt_jambi'` for basic Qt Jambi bindings only
|
30
|
+
>`require 'qt_connect'` for Qt Jambi bindings + QtRuby compatibility layer
|
31
|
+
|
32
|
+
|
33
|
+
### tested environments
|
34
|
+
|
35
|
+
**[Windows 7 Home Premium]**
|
36
|
+
>ruby 1.9.1p429 [i386-mingw32]
|
37
|
+
> qtbindings-4.6.3.2-x86-mingw32
|
38
|
+
> qt_connect-1.5.1
|
39
|
+
>
|
40
|
+
>jruby 1.6.7 [Windows 7-x86-java]
|
41
|
+
>qtjambi-4.7.1.jar
|
42
|
+
>qtjambi-win32-msvc2008-4.7.1.jar
|
43
|
+
>qt\_connect-1.5.1
|
44
|
+
|
45
|
+
**[Mac OS X : NOT TESTED]** ; *please report your experiences*
|
46
|
+
|
47
|
+
**[Linux : ubuntu 11:10]**
|
48
|
+
>ruby 1.8.7p352 [x86_64-linux]
|
49
|
+
>qtbindings-4.6.3.4
|
50
|
+
>qt\_connect-1.5.1
|
51
|
+
>
|
52
|
+
>jruby 1.5.1p249 [amd64-java]
|
53
|
+
>qtjambi-4.7.0.jar
|
54
|
+
>qtjambi-linux64-gcc-4.7.0.jar
|
55
|
+
>qt\_connect-1.5.1
|
56
|
+
|
57
|
+
>>Note: Under Ubuntu I could only run the programs using administrator privilege
|
58
|
+
>>e.g. sudo jruby ..... My Linux expertise is limited but it seems that
|
59
|
+
>>the operating system itself uses the Qt library and that maybe jruby loads (part of)
|
60
|
+
>>the qtjambi library not from the jar but from somewere else on the loadpath. I have
|
61
|
+
>>the same problem with the QtJambi demo programs if I just install QtJambi (nothing
|
62
|
+
>>to do with JRuby). Any help would be appreciated.
|
63
|
+
|
64
|
+
|
65
|
+
qt\_jambi
|
66
|
+
--------
|
67
|
+
Provides the basic Ruby bindings for the Qt Jambi Java classes and an interface to the
|
68
|
+
Signals and Slots mechanism. Appropriate classes are extended with overloaded operators
|
69
|
+
such as +, - , | and &. Both Ruby-ish underscore and Java-ish camel case naming for
|
70
|
+
methods can be used. Like with Qt Ruby, constructors can be called in a conventional
|
71
|
+
style or with an initializer block. If you want an API which follows closely the QtJambi
|
72
|
+
documentation and programming examples all you need to do is `'require qt_jambi'`. A
|
73
|
+
minimalist example to display just a button which connects to a website when clicked:
|
74
|
+
|
75
|
+
require 'qt_jambi'
|
76
|
+
|
77
|
+
app=Qt::Application.new(ARGV)
|
78
|
+
button=Qt::PushButton.new( "Hello World")
|
79
|
+
button.clicked.connect{ Qt::DesktopServices.openUrl(Qt::Url.new('www.hello-world.com'))}
|
80
|
+
button.show
|
81
|
+
app.exec`
|
82
|
+
|
83
|
+
|
84
|
+
The QtJambi documentation contains a large number of examples written in Java. Even with
|
85
|
+
a moderate knowledge of Java it is fairly easy to translate these to Ruby or
|
86
|
+
use them as examples for new Ruby programs.
|
87
|
+
|
88
|
+
qt\_compat
|
89
|
+
---------
|
90
|
+
A compatibility layer which can be installed on top of the 'qt\_jambi' bindings.
|
91
|
+
Including this layer makes it possible to run existing Qt Ruby code with no or only minor
|
92
|
+
modifications using JRuby.
|
93
|
+
Since the QtJambi API is build on the same underlying C++ library as the Qt Ruby API, the
|
94
|
+
Qt(C++) classes are mostly mapped to equivalent Qt Jambi(Java/Ruby) and Qt Ruby(Ruby)
|
95
|
+
classes. The majority of Ruby classes exposed through the Qt Jambi API are therefore
|
96
|
+
identical to the classes exposed through the Qt Ruby API. There are however differences
|
97
|
+
due to different language capabilities and limitations in converting C++ types to Java and
|
98
|
+
Ruby 'types'. The two most notable differences are the handling of Enums and the abstract
|
99
|
+
value type QVariant
|
100
|
+
|
101
|
+
### Enums
|
102
|
+
Enums are used widely throughout the Qt framework, mostly as integer constants and
|
103
|
+
bit flags. Using Qt Jambi, Java Enums are accessible through JRuby as constants. Individual
|
104
|
+
values are accessed combining namespace with enum type name and enum value name:
|
105
|
+
|
106
|
+
Qt::Alignment::AlignCenter
|
107
|
+
Qt::Alignment::AlignLeft
|
108
|
+
Qt::Font::StyleHint::Times
|
109
|
+
Qt::Font::StyleStrategy::ForceOutline
|
110
|
+
|
111
|
+
QtRuby accesses the same values using a shorthand notation omitting the type name:
|
112
|
+
|
113
|
+
Qt::AlignCenter
|
114
|
+
Qt::AlignLeft
|
115
|
+
Qt::Font::Times
|
116
|
+
Qt::Font::ForceOutline
|
117
|
+
|
118
|
+
Using the compatibility layer the same shorthand notation constants are defined in addition
|
119
|
+
to the existing ones. If the shorthand notation clashes with other entries or existing class
|
120
|
+
names, the default mapping can be overruled.
|
121
|
+
|
122
|
+
### QVariant
|
123
|
+
The Qt API provides an abstract value type, QVariant, which can hold values of many
|
124
|
+
C++ and Qt types. In Java - like Ruby - there is no real need for such a type because
|
125
|
+
the language already provides Object as an abstract type. This works because all Java
|
126
|
+
classes inherit Object. For that reason, the Qt Jambi API uses Object where Qt uses
|
127
|
+
QVariant.
|
128
|
+
To maintain code compatibility with QtRuby a dummy constructor method
|
129
|
+
is defined which basically returns the original object unchanged:
|
130
|
+
|
131
|
+
size=Qt::Size.new(100,200)
|
132
|
+
variant=Qt::Variant.new(size) #=> size
|
133
|
+
|
134
|
+
This works in many cases, but not always. QtRuby code using the Qt::Variant class may
|
135
|
+
need to be adjusted for use with the QtJambi bindings.
|
136
|
+
|
137
|
+
|
138
|
+
### Slots and Signals
|
139
|
+
Although QtJambi supports a simpler mechanism to deal with signals which does not need
|
140
|
+
the C++ signatures and uses methods or code blocks in stead of slots, a full set of compatible
|
141
|
+
routines to handle Slots and Signals is included for compatibility with QtRuby. This includes
|
142
|
+
slots and signals functions, SLOT and SIGNAL 'macros', connect, disconnect, emit and sender
|
143
|
+
methods:
|
144
|
+
|
145
|
+
slots 'documentWasModified()'
|
146
|
+
signals 'contentsChanged()'
|
147
|
+
|
148
|
+
connect(@textEdit.document(), SIGNAL('contentsChanged()'),
|
149
|
+
self, SLOT('documentWasModified()'))
|
150
|
+
|
151
|
+
emit contentsChanged
|
152
|
+
|
153
|
+
### Custom adjustments
|
154
|
+
A number of individual class definitions are 'tweaked' or extended to make the QtRuby API
|
155
|
+
fit on the QtJambi API. This is still very much work in progress
|
156
|
+
|
157
|
+
qt\_sugar
|
158
|
+
--------
|
159
|
+
|
160
|
+
Provides added functionality to the current signals/slots API in QtRuby
|
161
|
+
It avoids the need to use C++ signatures and signal/slots functions in applications.
|
162
|
+
The syntax is similar to the one used in QtJambi (the Java version of the Qt framework):
|
163
|
+
Each Signal-emitting Qt class is extended with a method for each Signal it may emit.
|
164
|
+
The default name of the method is the same as the signature name. In case of ambiguity
|
165
|
+
when the signal has arguments or overloads, the default name for a given signature can be
|
166
|
+
overruled through an entry in the **Signature2Signal** table. The initial entries in the table
|
167
|
+
are the same as used in the QtJambi interface.
|
168
|
+
Example:
|
169
|
+
|
170
|
+
|
171
|
+
require 'qt_connect'
|
172
|
+
|
173
|
+
button=Qt::PushButton.new
|
174
|
+
|
175
|
+
#connecting signal to a Ruby Block:
|
176
|
+
button.clicked.connect{ puts "Hello World"}
|
177
|
+
|
178
|
+
#connecting to receiver/method
|
179
|
+
button.clicked.connect(self,:methodname)
|
180
|
+
|
181
|
+
#disconnect all connections originating from this button
|
182
|
+
button.clicked.disconnect
|
183
|
+
|
184
|
+
#emitting the signal (normally done internally by Qt)
|
185
|
+
button.clicked.emit
|
186
|
+
|
187
|
+
#example passing String as argument
|
188
|
+
label=Qt::Label.new
|
189
|
+
label.linkActivated.connect{ |s| Qt::DesktopServices.openUrl(Qt::Url.new(s))}
|
190
|
+
|
191
|
+
Custom components can define their own signals and emit them like this:
|
192
|
+
|
193
|
+
@signal=Qt::Signal.new
|
194
|
+
@signal.connect{ |a1,a2| puts "someone send me #{a1} and #{a2}"}
|
195
|
+
.
|
196
|
+
.
|
197
|
+
@signal.emit(100,200)
|
198
|
+
|
199
|
+
background
|
200
|
+
-----------
|
201
|
+
For several years I have been an enthusiastic user of the excellent Ruby bindings to the Qt APIs as provided
|
202
|
+
by Richard Dale and others as part of the Korundum/QtRuby project. With the becoming of age of JRuby
|
203
|
+
I became interested in running my applications using JRuby, mainly for reasons of packaging and deployment.
|
204
|
+
The [qtjruby-core gem](https://github.com/nmerouze/qtjruby-core) provided by Nicolas Merouze
|
205
|
+
was a good start for a JRuby interface to QtJambi, but to get a reasonable compatibility with an existing
|
206
|
+
code base in QtRuby a lot more was required. I decided therefore to develop a new gem package which
|
207
|
+
would make it easy to port existing applications to JRuby or better use the same code for deployment
|
208
|
+
under Ruby and JRuby.
|
209
|
+
Because of the inherent differences between the two interfaces it will never be possible to have complete inter
|
210
|
+
operability, but it is certainly possible to write code in such a way that it will work with both Ruby and JRuby.
|
211
|
+
As a proof of principle I have a large Qt application (>10000 lines Ruby Code) which runs with the 'qt\_connect'
|
212
|
+
gem both under Ruby and JRuby. This allows me to do development under Ruby and package and deploy under JRuby.
|
213
|
+
|
214
|
+
Through the years I have seen several suggestions for changes to the slots and signals interface used by QtRuby.
|
215
|
+
The existing API is based on C++ signatures and not very appropriate for Ruby programming. The solution
|
216
|
+
provided by the QtJambi interface is quite elegant and in my opinion also suitable for Ruby. I have therefore included a
|
217
|
+
QtJambi based signals interface for Ruby programs in the gem package.
|
218
|
+
|
219
|
+
### examples and testing
|
220
|
+
The example programs should run both in a Ruby (qtbindings + qt\_connect gem packages) and a JRuby
|
221
|
+
(qt\_connect gem + Qt Jambi jars) setting. The Class Explorer is for JRuby users only.
|
222
|
+
This browser extends the Javadoc documentation for the com.trolltech.qt Javapackages that
|
223
|
+
form the basis for the JRuby Qt Jambi API.
|
224
|
+
If you install the Ruby qtbindings gem package, the example directory includes more than 75 example programs.
|
225
|
+
Of these programs about a third run unchanged with JRuby and the qt\_connect gem. Of the remaining programs
|
226
|
+
quite a few can not run for the same reason in that they make use of the Qt's resources system which is different
|
227
|
+
from the Qt Jambi resource management (these are all the examples using files with the .qrc extension). Some programs
|
228
|
+
run after minor modifications and more may run with appropriate extensions of the qt\_compat module, I have not had
|
229
|
+
much time to test this out.
|
230
|
+
|
231
|
+
### packaging and deployment
|
232
|
+
When it comes to packaging your Qt JRuby application, there is a great gem package to help you called
|
233
|
+
[Rawr](http://rawr.rubyforge.org). With Rawr, a simple, pre-generated configuration file turns your code into
|
234
|
+
an executable jar, a .exe for Windows, and a .app for OS X.
|
235
|
+
|
236
|
+
### documentation
|
237
|
+
If you install the full Qt Jambi package, it comes with extensive documentation for the Java API and a large number
|
238
|
+
of Java demonstration programs. Use these together with the class explorer program (*part of this gem package*) to write
|
239
|
+
your Ruby programs.
|
240
|
+
The example programs (*deform.rb*, *qtmapzoom.rb* and *classexplorer.rb*) also show how to program the signal interface
|
241
|
+
in Ruby.
|
242
|
+
For Qt Ruby programming in general have a look at the demonstration programs included with the qtbindings gem package.
|
243
|
+
There is also an ebook
|
244
|
+
([Rapid GUI Development with QtRuby](http://pragprog.com/book/ctrubyqt/rapid-gui-development-with-qtruby)
|
245
|
+
by Caleb Tennis) published by The Pragmatic Bookshelf. Although the book itself is somewhat out of date, it is still a good introduction
|
246
|
+
to Qt Ruby programming.
|
247
|
+
In addition to the [Qt Jambi documentation](http://doc.trolltech.com/qtjambi-4.4/html) there is also a lot of Qt documentation
|
248
|
+
online at the [Qt Developer Network](http://qt-project.org).
|
249
|
+
For information about JRuby look at <http://jruby.org> , read the [JRuby Cookbook](http://shop.oreilly.com/product/9780596519650.do)
|
250
|
+
published by O'Reilly or [Using JRuby](http://pragprog.com/book/jruby/using-jruby) from The Pragmatic Bookshelf.
|
251
|
+
|
252
|
+
## license
|
253
|
+
|
254
|
+
Copyright © 2010-2012 Cees Zeelenberg (<c.zeelenberg@computer.org>)
|
255
|
+
This software is released under the LGPL Version 2.1.
|
256
|
+
See COPYING.LIB.txt
|
257
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#
|
2
|
+
# Created by Cees Zeelenberg
|
3
|
+
# Copyright (c) 2012. All rights reserved.
|
4
|
+
#
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
|
8
|
+
$:.unshift(File.join(Dir.pwd,"lib"))
|
9
|
+
require 'qt_connect/version'
|
10
|
+
|
11
|
+
task :default => [:jar]
|
12
|
+
CLEAN.include '*.class','Manifest'
|
13
|
+
CLOBBER.include '*.jar'
|
14
|
+
|
15
|
+
JAVA_FILES=FileList['java/*.java']
|
16
|
+
CLASS_FILES=JAVA_FILES.pathmap("%n").ext('class')
|
17
|
+
|
18
|
+
#directory 'package_dir'
|
19
|
+
|
20
|
+
rule '.class' => [proc {|s| x=s.sub(/\.[^.]+$/, '.java') ; 'java/'+x}] do |t|
|
21
|
+
sh "javac -target 1.6 #{t.source}"
|
22
|
+
puts f=t.source.ext('class')
|
23
|
+
verbose(true) { mv "#{f}", "."}
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
desc 'Build the .jar file for the Gem'
|
28
|
+
task :jar => [CLASS_FILES, 'Manifest'] do
|
29
|
+
sh 'jar -cfm lib/signalactions.jar Manifest SignalActions.class'
|
30
|
+
rm 'Manifest'
|
31
|
+
rm 'Signalactions.class'
|
32
|
+
end
|
33
|
+
|
34
|
+
file 'Manifest' do
|
35
|
+
File.open('Manifest','w'){ |f| f.puts "Main-Class: SignalActions"}
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#~ desc 'Build the Gem package'
|
40
|
+
#~ task :build_package => [:jar,'package_dir'] do
|
41
|
+
gem = Gem::Specification.new do |s|
|
42
|
+
s.name ='qt_connect'
|
43
|
+
s.version = QtJambi::Version
|
44
|
+
s.date ='2012-05-16'
|
45
|
+
s.summary ='Uniform Qt bindings for JRuby and Ruby '
|
46
|
+
s.authors =['Cees Zeelenberg']
|
47
|
+
s.homepage = "https://github.com/CeesZ/qt_connect"
|
48
|
+
s.email ='c.zeelenberg@computer.org'
|
49
|
+
s.licenses = ["LGPLv2.1"]
|
50
|
+
s.files =Dir['lib/**/*.rb']
|
51
|
+
s.files +=Dir['java/**/*.java']
|
52
|
+
s.files +=Dir['lib/**/*.jar']
|
53
|
+
s.files +=Dir['examples/**/*.*']
|
54
|
+
s.files +=Dir['[A-Z]*']
|
55
|
+
s.require_paths= ["lib"]
|
56
|
+
s.description =
|
57
|
+
%Q[Qt bindings for JRuby with (optional) extension to provide backward
|
58
|
+
compatibility with Qt Ruby programs. The API is implemented using Qt Jambi, the
|
59
|
+
Java version of the Qt framework. For Qt Ruby programs it provides a QtJambi
|
60
|
+
inspired interface to the Signals/Slots system without the need to use
|
61
|
+
C++ signatures.
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
Rake::GemPackageTask.new(gem) do |pkg|
|
66
|
+
#pkg.need_zip = true
|
67
|
+
#pkg.need_tar = true
|
68
|
+
#pkg.gem_spec = spec
|
69
|
+
#pkg.package_files << 'y'
|
70
|
+
end
|
71
|
+
#~ end
|
72
|
+
|
@@ -0,0 +1,828 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$KCODE = 'UTF8' if RUBY_VERSION =~ /1\.8/
|
3
|
+
=begin
|
4
|
+
|
5
|
+
Author: Cees Zeelenberg (c.zeelenberg@computer.org)
|
6
|
+
Copyright: (c) 2011-2012 by Cees Zeelenberg
|
7
|
+
License: This program is free software; you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU Lesser General Public License as published
|
9
|
+
by the Free Software Foundation; either version 2 of the License, or
|
10
|
+
(at your option) any later version. *
|
11
|
+
=end
|
12
|
+
|
13
|
+
unless RUBY_PLATFORM =~ /java/
|
14
|
+
puts "Sorry!"
|
15
|
+
puts "Class Explorer is an application"
|
16
|
+
puts "for browsing Qt Jambi class definitions"
|
17
|
+
puts "it can only be run using JRuby"
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'qt_connect'
|
22
|
+
require 'qt_extensions'
|
23
|
+
require 'menus'
|
24
|
+
require 'packagetree'
|
25
|
+
|
26
|
+
class ClassExplorer < Qt::MainWindow
|
27
|
+
|
28
|
+
MARKERS=[
|
29
|
+
[:"final?", 'Final Java method', '#81F7F3'],
|
30
|
+
[:"abstract?", 'Abstract Java method', '#F5D0A9'],
|
31
|
+
[:"ruby?", 'Ruby method', 'yellow']]
|
32
|
+
|
33
|
+
BGCOLOR="#ccccff"
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
|
37
|
+
super
|
38
|
+
setup_UI
|
39
|
+
setup_panels
|
40
|
+
setup_menu_bar
|
41
|
+
create_dock_window
|
42
|
+
read_settings
|
43
|
+
@java_root_edit.text=@root
|
44
|
+
@java_root_edit.textChanged.connect{ |s| @root=s }
|
45
|
+
@java_root_edit.editing_finished.connect{ show_class(@lastpath) if @lastpath}
|
46
|
+
@packagetree=PackageTree.new('com.trolltech.qt')
|
47
|
+
@package_list.model=PackageModel.new(@packagetree)
|
48
|
+
@package_list.clicked.connect( self,:on_package_clicked)
|
49
|
+
@package_list.selectionModel=Qt::ItemSelectionModel.new(@package_list.model)
|
50
|
+
#use stylesheet to set selection-background-color
|
51
|
+
#default background color is wrong if item selected but not in focus
|
52
|
+
@package_list.styleSheet=%Q[selection-background-color: #3399ff; selection-color: white;]
|
53
|
+
|
54
|
+
@class_list.model=FilteredClassModel.new(@filter_line_edit)
|
55
|
+
@class_list.clicked.connect(self,:on_class_clicked)
|
56
|
+
@class_list.selectionModel=Qt::ItemSelectionModel.new(@class_list.model)
|
57
|
+
@class_list.styleSheet=%Q[selection-background-color: #3399ff; selection-color: white;]
|
58
|
+
|
59
|
+
MethodAttributes.namespace{ |method| rubyname(method) }
|
60
|
+
|
61
|
+
Qt::Internal::RUBY_extensions.each{ |method|
|
62
|
+
mas=MethodAttributes.new(method)
|
63
|
+
mas.lang=:ruby
|
64
|
+
@newmethods[mas.receiver] ||=[]
|
65
|
+
@newmethods[mas.receiver] << mas
|
66
|
+
}
|
67
|
+
#initial selection: all packages
|
68
|
+
@package_list.select_all
|
69
|
+
update_class_panel
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup_UI
|
74
|
+
resize(1000,768)
|
75
|
+
# the widgets composing the UI
|
76
|
+
@central_widget=Qt::Widget.new
|
77
|
+
@caption=Qt::Label.new('')
|
78
|
+
@package_list=Qt::ListView.new
|
79
|
+
@class_list=Qt::ListView.new
|
80
|
+
@detail_panel=Qt::TextBrowser.new
|
81
|
+
@nav_left=Qt::ToolButton.new{ |tb| tb.icon=style.standardIcon(Qt::Style::SP_ArrowLeft)}
|
82
|
+
@nav_right=Qt::ToolButton.new{ |tb| tb.icon=style.standardIcon(Qt::Style::SP_ArrowRight)}
|
83
|
+
@inherit_box=Qt::CheckBox.new('include inherited Qt members')
|
84
|
+
@linkto_box=Qt::CheckBox.new('link to Qt Jambi Reference Documentation')
|
85
|
+
@filter_line_edit=QtExtensions::LineEdit.new
|
86
|
+
|
87
|
+
#the layout for the widgets
|
88
|
+
options_layout=Qt::VBoxLayout.new{ |v|
|
89
|
+
v.add_widget(@inherit_box,0)
|
90
|
+
v.add_widget(@linkto_box,0)
|
91
|
+
v.add_stretch(100)
|
92
|
+
}
|
93
|
+
|
94
|
+
navigate_layout=Qt::HBoxLayout.new{ |h|
|
95
|
+
h.add_widget(@nav_left,0)
|
96
|
+
h.add_widget(@nav_right,0)
|
97
|
+
h.add_stretch(100)
|
98
|
+
}
|
99
|
+
|
100
|
+
header_layout=Qt::VBoxLayout.new{ |v|
|
101
|
+
v.add_layout(navigate_layout,0)
|
102
|
+
v.add_widget(@caption,99)
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
footer=Qt::Label.new(html_footer)
|
107
|
+
|
108
|
+
headerframe_layout=Qt::HBoxLayout.new{ |h|
|
109
|
+
h.add_layout(header_layout,99)
|
110
|
+
h.add_layout(options_layout,0)
|
111
|
+
|
112
|
+
}
|
113
|
+
|
114
|
+
leftframe=Qt::Widget.new { |f|
|
115
|
+
#f.frame_style=Qt::Frame::Panel | Qt::Frame::Plain
|
116
|
+
f.layout=Qt::VBoxLayout.new { |v|
|
117
|
+
v.add_widget(@package_list,30)
|
118
|
+
v.add_widget(@class_list,70)
|
119
|
+
v.add_widget(@filter_line_edit,0)
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
rightframe=Qt::Widget.new { |f|
|
124
|
+
#f.frame_style=Qt::Frame::Panel | Qt::Frame::Plain
|
125
|
+
f.layout=Qt::VBoxLayout.new { |v|
|
126
|
+
v.add_layout(headerframe_layout,0)
|
127
|
+
v.add_widget(@detail_panel,99)
|
128
|
+
v.add_widget(footer,0)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
#use a splitter as a flexible divider between left and right frames
|
132
|
+
@splitter=splitter=Qt::Splitter.new(Qt::Horizontal){ |s|
|
133
|
+
s.add_widget(leftframe)
|
134
|
+
s.add_widget(rightframe)
|
135
|
+
}
|
136
|
+
@splitter.sizes=[200,800]
|
137
|
+
|
138
|
+
@central_widget.layout=Qt::VBoxLayout.new { |v|
|
139
|
+
v.add_widget(@splitter,100)
|
140
|
+
}
|
141
|
+
self.central_widget=@central_widget
|
142
|
+
end
|
143
|
+
def setup_panels
|
144
|
+
@package_list.view_mode=Qt::ListView::ListMode
|
145
|
+
@package_list.selection_mode=Qt::ListView::SelectionMode::ExtendedSelection
|
146
|
+
@package_list.selectionRectVisible=true
|
147
|
+
@class_list.itemDelegate=QtExtensions::HtmlDelegate.new
|
148
|
+
@inherit_box.state_changed.connect(self,:html_class_description)
|
149
|
+
@linkto_box.toggled.connect{ |tf|
|
150
|
+
next unless @dock
|
151
|
+
@dock.visible=tf unless @dock.visible==tf
|
152
|
+
show_class(@lastpath) if @lastpath && @dock.visible?
|
153
|
+
}
|
154
|
+
@newmethods={}
|
155
|
+
@detail_panel.openLinks=false
|
156
|
+
@detail_panel.anchorClicked.connect(self,:on_class_navigate)
|
157
|
+
@filter_line_edit.placeholderText="Filter"
|
158
|
+
@filter_line_edit.setClearButton
|
159
|
+
@navigate=Navigator.new
|
160
|
+
@navigate.browse{ |path| show_class(path)}
|
161
|
+
@navigate.buttons(@nav_left,@nav_right)
|
162
|
+
end
|
163
|
+
|
164
|
+
def setup_menu_bar
|
165
|
+
@menubar=Qt::MenuBar.new(self) { |menubar|
|
166
|
+
setMenuBar(menubar)
|
167
|
+
@helpmenu=Menus::HelpMenu.new(self,menubar)
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_dock_window
|
172
|
+
java_label=Qt::Label.new('Javadoc root:')
|
173
|
+
@java_root_edit=Qt::LineEdit.new
|
174
|
+
@java_url_edit=Qt::LineEdit.new
|
175
|
+
|
176
|
+
@jambibrowser=Qt::WebView.new
|
177
|
+
@jambibrowser.html=''
|
178
|
+
javadoc_header=Qt::Widget.new{ |w|
|
179
|
+
w.layout=Qt::HBoxLayout.new { |h|
|
180
|
+
h.add_widget(java_label,0)
|
181
|
+
h.add_widget(@java_root_edit,99)
|
182
|
+
}
|
183
|
+
}
|
184
|
+
@javadoc=Qt::Widget.new{ |w|
|
185
|
+
w.layout=Qt::VBoxLayout.new{ |v|
|
186
|
+
v.add_widget(javadoc_header,0)
|
187
|
+
v.add_widget(@java_url_edit,0)
|
188
|
+
v.add_widget(@jambibrowser,99)
|
189
|
+
}
|
190
|
+
}
|
191
|
+
@dock = Qt::DockWidget.new("Qt Jambi Reference Documentation", self)
|
192
|
+
@dock.objectName="Record_Browser"
|
193
|
+
@dock.minimum_width=500
|
194
|
+
@dock.allowedAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea
|
195
|
+
|
196
|
+
@dock.widget = @javadoc
|
197
|
+
addDockWidget(Qt::RightDockWidgetArea, @dock)
|
198
|
+
@dock.visibilityChanged.connect{ |tf| @linkto_box.checked=tf unless @linkto_box.checked==tf}
|
199
|
+
end
|
200
|
+
|
201
|
+
def read_settings
|
202
|
+
settings = Qt::Settings.new("Qt_connect", "Class Explorer")
|
203
|
+
pos = settings.value("pos", Qt::Point.new(200, 200))
|
204
|
+
size = settings.value("size", Qt::Size.new(1000,768))
|
205
|
+
@linkto_box.checked=Qt::Variant.toBoolean(settings.value('linkto_box')) || false
|
206
|
+
@inherit_box.checked=Qt::Variant.toBoolean(settings.value('inherit_box')) || false
|
207
|
+
@splitter.sizes=Qt::Variant.toList(settings.value("splitter")) || [200,800]
|
208
|
+
@root=Qt::Variant.toString(settings.value("root")) || "http://doc.trolltech.com/qtjambi-4.4/html"
|
209
|
+
state=settings.value("state")
|
210
|
+
resize(size)
|
211
|
+
move(pos)
|
212
|
+
restoreState(state) if state
|
213
|
+
end
|
214
|
+
|
215
|
+
#overrule (N.B. must use camelcase here!)
|
216
|
+
def closeEvent(event)
|
217
|
+
settings = Qt::Settings.new("Qt_connect", "Class Explorer")
|
218
|
+
settings.setValue("pos",pos)
|
219
|
+
settings.setValue("size",size)
|
220
|
+
settings.setValue("state",saveState)
|
221
|
+
settings.setValue("linkto_box",@linkto_box.checked?)
|
222
|
+
settings.setValue("inherit_box",@inherit_box.checked?)
|
223
|
+
settings.setValue("splitter",@splitter.sizes)
|
224
|
+
settings.setValue("root",@root)
|
225
|
+
super(event)
|
226
|
+
end
|
227
|
+
|
228
|
+
def on_package_clicked(index)
|
229
|
+
update_class_panel
|
230
|
+
ix0=@class_list.model.index(0,0)
|
231
|
+
@class_list.selectionModel.select(ix0,Qt::ItemSelectionModel::SelectionFlag::ClearAndSelect)
|
232
|
+
on_class_clicked(ix0)
|
233
|
+
end
|
234
|
+
def update_class_panel
|
235
|
+
classes=@package_list.selectedIndexes.
|
236
|
+
inject([]){ |memo,ix| memo += @package_list.model.classes(ix).
|
237
|
+
map{ |c| rubyname(@package_list.model.data(ix) + '.' +c)}}.
|
238
|
+
sort
|
239
|
+
@class_list.clear_selection
|
240
|
+
@class_list.model.layout_about_to_be_changed.emit
|
241
|
+
@class_list.model.classes=classes
|
242
|
+
@class_list.model.layout_changed.emit
|
243
|
+
@filter_line_edit.text=''
|
244
|
+
end
|
245
|
+
|
246
|
+
def on_class_clicked(index)
|
247
|
+
begin
|
248
|
+
@rubyname=@class_list.model.data(index,Qt::UserRole)
|
249
|
+
@klass=eval(@rubyname)
|
250
|
+
@klass=qt_hierarchy(@klass)[-1]
|
251
|
+
@path=@klass.java_class.name
|
252
|
+
rescue => e
|
253
|
+
puts "error on_class_clicked: #{e}"
|
254
|
+
puts caller
|
255
|
+
return
|
256
|
+
end
|
257
|
+
path=@path.gsub('.','/').gsub('$','.')
|
258
|
+
navigate_to_class(path)
|
259
|
+
end
|
260
|
+
|
261
|
+
#url: (QUrl) com/trolltech/qt/core/Qt.AlignmentFlag => path: "com/trolltech/qt/gui/Qt.AlignmentFlag"
|
262
|
+
#url: (QUrl) com/trolltech/qt/gui/QAccessibleInterface => path: "com/trolltech/qt/gui/QAccessibleInterface"
|
263
|
+
#url: (QUrl) com/trolltech/qt/gui/QAccessible.Method => path: "com/trolltech/qt/gui/QAccessible.Method"
|
264
|
+
def on_class_navigate(url)
|
265
|
+
path=url.path
|
266
|
+
@klass=eval(path.gsub('.','::').gsub('/','.')) rescue return
|
267
|
+
@rubyname=rubyname(@klass)
|
268
|
+
@path=@klass.java_class.name
|
269
|
+
|
270
|
+
model=@package_list.model
|
271
|
+
elems=@path.split('.')
|
272
|
+
klass_name=elems.pop
|
273
|
+
package_name=elems.join('.')
|
274
|
+
return unless ix=(0...model.rowCount(0)).map{ |r| model.index(r,0)}.detect{ |m| model.data(m)==package_name}
|
275
|
+
#if package present, but NOT selected in package_panel; select it now and update class_panel
|
276
|
+
unless @package_list.selectedIndexes.detect{ |m| m==ix}
|
277
|
+
@package_list.selectionModel.select(ix,Qt::ItemSelectionModel::SelectionFlag::Select)
|
278
|
+
update_class_panel
|
279
|
+
end
|
280
|
+
|
281
|
+
@filter_line_edit.text=''
|
282
|
+
model=@class_list.model
|
283
|
+
return unless ix=(0...model.rowCount(0)).map{ |r| model.index(r,0)}.detect{ |m| model.data(m)==@rubyname}
|
284
|
+
@class_list.selectionModel.select(ix,Qt::ItemSelectionModel::SelectionFlag::ClearAndSelect)
|
285
|
+
@class_list.scrollTo(ix)
|
286
|
+
on_class_clicked(ix)
|
287
|
+
#navigate_to_class(path)
|
288
|
+
end
|
289
|
+
|
290
|
+
def navigate_to_class(path)
|
291
|
+
@navigate.push(path)
|
292
|
+
show_class(path)
|
293
|
+
end
|
294
|
+
|
295
|
+
def show_class(path)
|
296
|
+
@klass=eval(path.gsub('.','::').gsub('/','.')) rescue return
|
297
|
+
@rubyname=rubyname(@klass)
|
298
|
+
@path=@klass.java_class.name
|
299
|
+
@lastpath=path
|
300
|
+
@caption.text=html_header
|
301
|
+
html_class_description
|
302
|
+
url=Qt::Url.new(%Q[#{@root}/]+path+".html")
|
303
|
+
return unless @linkto_box.checked?
|
304
|
+
begin
|
305
|
+
@jambibrowser.load(url)
|
306
|
+
@java_url_edit.text=url.toString
|
307
|
+
rescue => e
|
308
|
+
puts "error on_textbrowser: #{e}"
|
309
|
+
print e.backtrace.join("\n")
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
#
|
314
|
+
#klass.class:
|
315
|
+
#Module, Class: return name in Ruby namespace notation (e.g Qt::Widget, Qt::AbstractItemView::ConcreteWrapper)
|
316
|
+
#String: convert all class references in string to Ruby namespace notation:
|
317
|
+
# com.trolltech.qt.gui.QAbstractItemView$ConcreteWrapper => Qt::AbstractItemView::ConcreteWrapper
|
318
|
+
def rubyname(klass)
|
319
|
+
if klass.respond_to?(:java_class)
|
320
|
+
qtname=klass.java_class.name
|
321
|
+
elsif klass.kind_of? String
|
322
|
+
qtname="#{klass}"
|
323
|
+
else #Ruby defined class
|
324
|
+
return klass.name
|
325
|
+
end
|
326
|
+
#remove longest matchingpackage (e.g. 'com.trolltech.qt.gui.QAbstractButton' => 'Qt:AbstractButton')
|
327
|
+
@packagetree.keys.sort{ |a,b| b.size <=> a.size}.
|
328
|
+
each{ |package_name| qtname.gsub!(package_name + '.','Qt::')}
|
329
|
+
|
330
|
+
qtname=qtname.gsub('$','::').
|
331
|
+
gsub('Qt::Qt','Qt::').
|
332
|
+
gsub('Qt::Q','Qt::').
|
333
|
+
gsub('Qt::::','Qt::')
|
334
|
+
qtname=qtname[0..-3] if qtname.end_with?('::')
|
335
|
+
return qtname
|
336
|
+
end
|
337
|
+
|
338
|
+
def html_header
|
339
|
+
%Q[<h2>
|
340
|
+
<font size="-1">#{@packagetree.jars}
|
341
|
+
<br>
|
342
|
+
#{@path}</font>
|
343
|
+
<br>
|
344
|
+
#{@klass.class} #{@rubyname}</h2>]
|
345
|
+
end
|
346
|
+
|
347
|
+
def html_footer
|
348
|
+
html='<table border="1" width="100%" cellpadding="3" cellspacing="0"><tr>'
|
349
|
+
MARKERS.each{ |test,name,color| html+="<th bgcolor='#{color}'>#{name}</th>"}
|
350
|
+
html+='</tr></table>'
|
351
|
+
return html
|
352
|
+
end
|
353
|
+
|
354
|
+
#delete 'constants' from class_methods (static fields which implement constants)
|
355
|
+
def html_class_description
|
356
|
+
return unless @klass
|
357
|
+
sort_methods
|
358
|
+
@detail_panel.html= %Q[
|
359
|
+
#{html_ancestors}
|
360
|
+
<table border="1" width="100%" cellpadding="3" cellspacing="0" summary="">
|
361
|
+
#{html_enums}
|
362
|
+
#{html_constants}
|
363
|
+
#{html_nested_classes}
|
364
|
+
#{html_methods(@class_methods.delete_if{ |mas| @constants.include?(mas.methodname)},"#{@klass.class} Methods")}
|
365
|
+
#{html_signals}
|
366
|
+
#{html_methods(@instance_methods,"Instance Methods")}
|
367
|
+
</table>
|
368
|
+
]
|
369
|
+
end
|
370
|
+
|
371
|
+
#Escape XML special characters <, & and >
|
372
|
+
def escape(str)
|
373
|
+
str.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
374
|
+
end
|
375
|
+
|
376
|
+
def qt_ancestors(klass)
|
377
|
+
#klass.ancestors.delete_if{ |x| (!x.name.start_with? "Java::ComTrolltechQt")}
|
378
|
+
return [] unless klass.respond_to? :ancestors
|
379
|
+
klass.ancestors.select{ |x| x.name.start_with? "Java::ComTrolltechQt"}
|
380
|
+
end
|
381
|
+
|
382
|
+
def qt_hierarchy(klass)
|
383
|
+
qt_ancestors(klass).
|
384
|
+
map{ |ancestor| [ancestor,qt_ancestors(ancestor).size]}.
|
385
|
+
sort{|a,b| a[1]<=>b[1]}.
|
386
|
+
map{ |a,s| a}
|
387
|
+
end
|
388
|
+
|
389
|
+
def html_ancestors
|
390
|
+
html=%Q[<table border="0" cellpadding="4" cellspacing="0"><tr>]
|
391
|
+
modules,classes=qt_hierarchy(@klass).
|
392
|
+
partition{ |a| a.class==Module}.
|
393
|
+
map{ |klasses|
|
394
|
+
spaces=''
|
395
|
+
inherit=' '
|
396
|
+
klasses.map{ |x| leader=spaces+inherit ; spaces+=' '; inherit='┖' ; leader + html_qt_marker(rubyname(x))}.
|
397
|
+
join("\n")
|
398
|
+
}
|
399
|
+
|
400
|
+
if modules.size==0
|
401
|
+
return html + "<th>Class Hierarchy</th><td><pre>#{classes}</pre></td></tr></table>"
|
402
|
+
elsif classes.size==0
|
403
|
+
return html + "<th>Module Hierarchy</th><td><pre>#{modules}</pre></td></tr></table>"
|
404
|
+
else
|
405
|
+
return html + %Q[<th>Class Hierarchy:</th><td><pre>#{classes}</pre></td>
|
406
|
+
<th>Mixes in:</th>
|
407
|
+
<td><pre>#{modules}</pre></td>
|
408
|
+
</tr>
|
409
|
+
</table>]
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def html_constants
|
414
|
+
|
415
|
+
html=@constants.
|
416
|
+
map{ |c| v=@klass.const_get(c)
|
417
|
+
val="#{v}"
|
418
|
+
name=(v.respond_to?(:java_class)) ? v.java_class.name : v.class.name
|
419
|
+
if v.respond_to?(:value)
|
420
|
+
val=" 0x%08x" % v.value
|
421
|
+
elsif v.kind_of?(String)
|
422
|
+
val='"' + v + '"'
|
423
|
+
end
|
424
|
+
[c.to_s,val,name]}.
|
425
|
+
sort{ |a,b| (a[2]+a[0]) <=> (b[2]+b[0])}.
|
426
|
+
uniq.
|
427
|
+
delete_if{|xx| xx[0].start_with?('_')}.
|
428
|
+
map{ |xx| "<tr><td>#{rubyname(xx[2])}</td><td>#{xx[0]}</td><td>#{xx[1]}</td></tr>"}
|
429
|
+
return (html.size>0) ? %Q[
|
430
|
+
<!-- =========================== Class Constants =========================== -->
|
431
|
+
<tr bgcolor="#{BGCOLOR}">
|
432
|
+
<th align="left" colspan="3"><font size="+2">
|
433
|
+
<b>Constants</b></font></th>
|
434
|
+
</tr>
|
435
|
+
<font size='-1'><tr><th>class</th><th>object</th><th>value</th></tr></font>
|
436
|
+
#{html}
|
437
|
+
] : ''
|
438
|
+
end
|
439
|
+
|
440
|
+
def html_enums
|
441
|
+
html=''
|
442
|
+
@constants=[]
|
443
|
+
@enums=[]
|
444
|
+
@ignore=[]
|
445
|
+
@nested_classes=[]
|
446
|
+
konstants=@klass.constants
|
447
|
+
konstants.each{ |c|
|
448
|
+
ancestors=(eval("#{@klass.name}::#{c}.respond_to? :ancestors")) ? eval("#{@klass.name}::#{c}.ancestors"): []
|
449
|
+
if ancestors.include? java.lang.Enum
|
450
|
+
@enums << c
|
451
|
+
elsif (v=@klass.const_get(c)) && (v.class==Class)
|
452
|
+
@nested_classes << c
|
453
|
+
else
|
454
|
+
@constants << c
|
455
|
+
end
|
456
|
+
}
|
457
|
+
|
458
|
+
@enums.sort.each{ |enum|
|
459
|
+
values=eval(%Q[#{@klass.name}::#{enum}.values]).to_ary
|
460
|
+
enumname=html_qt_marker("#{@rubyname}::#{enum}")
|
461
|
+
html += "<tr><td rowspan='#{values.size}'>#{enumname}</td>"
|
462
|
+
tr=''
|
463
|
+
values.each{ |v|
|
464
|
+
s="#{v}"
|
465
|
+
fullname="#{@rubyname}::#{enum}::#{v}"
|
466
|
+
shortname="#{@rubyname}::#{v}"
|
467
|
+
name = if (Qt::Internal::DONT_ABBREVIATE[fullname]) || (s=~/\A[^A-Z].*\Z/)
|
468
|
+
#!const_defined?(shortname.to_sym)
|
469
|
+
"<font color='red'>#{fullname}</font>"
|
470
|
+
else
|
471
|
+
shortname
|
472
|
+
end
|
473
|
+
html+="#{tr}<td>#{v}</td><td>#{name}</td></tr>"
|
474
|
+
tr="<tr>"
|
475
|
+
@ignore << "#{v}"
|
476
|
+
}
|
477
|
+
}
|
478
|
+
@constants -= @ignore
|
479
|
+
|
480
|
+
return html unless html.size > 0
|
481
|
+
return %Q[
|
482
|
+
<!-- =========================== Class Enums =========================== -->
|
483
|
+
<tr bgcolor="#{BGCOLOR}">
|
484
|
+
<th align="left" colspan="3"><font size="+2">
|
485
|
+
<b>Enums</b></font></th>
|
486
|
+
</tr>
|
487
|
+
<font size='-1'><tr><th>enumerator</th><th>values</th><th>shorthand</th></tr>#{html}</font>
|
488
|
+
]
|
489
|
+
end
|
490
|
+
|
491
|
+
def html_nested_classes
|
492
|
+
html=@nested_classes.
|
493
|
+
map{ |c| rubyname(@klass.const_get(c))}.
|
494
|
+
delete_if{|name| name=='Qt::'}.
|
495
|
+
delete_if{|name| !@inherit_box.is_checked? && !name.start_with?(@rubyname)}.
|
496
|
+
sort.compact.
|
497
|
+
map{ |name| html_qt_marker(name)}.
|
498
|
+
join("\n")
|
499
|
+
return (html.size>0) ? %Q[
|
500
|
+
<!-- =========================== Nested Classes =========================== -->
|
501
|
+
<tr bgcolor="#{BGCOLOR}">
|
502
|
+
<th align="left" colspan="3"><font size="+2">
|
503
|
+
<b>Nested classes</b></font></th>
|
504
|
+
</tr>
|
505
|
+
<tr></td><td colspan="3">#{html}</td></tr>
|
506
|
+
] : ''
|
507
|
+
end
|
508
|
+
|
509
|
+
def sort_methods
|
510
|
+
@class_methods=[]
|
511
|
+
@instance_methods=[]
|
512
|
+
@fields=[]
|
513
|
+
@signals={}
|
514
|
+
rubynew=false
|
515
|
+
if @klass.respond_to?(:java_class) && javaclass=@klass.java_class
|
516
|
+
javamethods=org.jruby.javasupport.JavaClass.getMethods(javaclass).to_a
|
517
|
+
qt_ancestors(@klass).each{ |klass|
|
518
|
+
rname=rubyname(klass)
|
519
|
+
javamethods+=(@newmethods[rname] || [])
|
520
|
+
}
|
521
|
+
javamethods.each{ |javamethod|
|
522
|
+
mas=(javamethod.kind_of? MethodAttributes) ? javamethod : MethodAttributes.new(javamethod)
|
523
|
+
next if mas.native? #skip fromNativePointer etc.
|
524
|
+
next if mas.methodname.start_with? "__qt" #in case not labeled 'native'
|
525
|
+
next if !@inherit_box.is_checked? && (mas.receiver!=@rubyname)
|
526
|
+
|
527
|
+
if mas.private?
|
528
|
+
n=(mas.argtypes.size>0) ? mas.argtypes.count(',')+1 : 0
|
529
|
+
@signals[mas.methodname+n.to_s]=(mas.argtypes.size==0) ? '()' : mas.argtypes
|
530
|
+
elsif mas.static?
|
531
|
+
@class_methods << mas
|
532
|
+
rubynew=true if ((mas.methodname=='new') && (mas.lang==:ruby))
|
533
|
+
else
|
534
|
+
@instance_methods << mas
|
535
|
+
end
|
536
|
+
}
|
537
|
+
org.jruby.javasupport.JavaClass.getFields(javaclass).each{ |javamethod|
|
538
|
+
mas=MethodAttributes.new(javamethod)
|
539
|
+
next if mas.methodname.start_with? "_"
|
540
|
+
next if !@inherit_box.is_checked? && (mas.receiver!=@rubyname)
|
541
|
+
if (mas.returntype=~ /SignalEmitter/)
|
542
|
+
@fields << mas
|
543
|
+
elsif mas.static?
|
544
|
+
@class_methods << mas
|
545
|
+
else
|
546
|
+
@instance_methods << mas
|
547
|
+
end
|
548
|
+
}
|
549
|
+
#ignore java constructors if 'new' class method was redefined (e.g. rubynew==true)
|
550
|
+
if ((@klass.class==Class) && !rubynew)
|
551
|
+
javaclass.constructors.map{ |c|
|
552
|
+
mas=MethodAttributes.new(c)
|
553
|
+
mas.returntype=mas.methodname
|
554
|
+
mas.methodname='new'
|
555
|
+
mas.lang=:ruby
|
556
|
+
@class_methods<<mas
|
557
|
+
}
|
558
|
+
end
|
559
|
+
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
def html_methods(methods,caption)
|
564
|
+
html=''
|
565
|
+
methods.sort{ |a,b| a.methodname <=> b.methodname}.each{ |method|
|
566
|
+
next if (receiver=method.receiver) && !receiver.start_with?('Qt::') #only show inheritance from Qt members
|
567
|
+
bgcolor=''
|
568
|
+
MARKERS.each{ |test,name,color| bgcolor=color if method.send(test)}
|
569
|
+
html+=%Q[<tr>]
|
570
|
+
html+="<td>" + html_qt_marker(method.returntype) + "</td>"
|
571
|
+
html+="<td bgcolor='#{bgcolor}'><b>" + escape(method.methodname) + "</b>"
|
572
|
+
|
573
|
+
if (method.argtypes.size>0)
|
574
|
+
html += '(' + method.argtypes[1..-2].split(',').map{ |arg| html_qt_marker(arg)}.join(', ') +')'
|
575
|
+
end
|
576
|
+
html+="</td>"
|
577
|
+
#html+= method.annotations ? escape(" {#{method.annotations}}") : ''
|
578
|
+
html+=(method.receiver==@rubyname) ? '<td></td>' : "<td>#{html_qt_marker(method.receiver||'')}</td>" if @inherit_box.is_checked?
|
579
|
+
html+="</tr>"
|
580
|
+
}
|
581
|
+
return (html.size>0) ? %Q[
|
582
|
+
<!-- =========================== Instance Methods =========================== -->
|
583
|
+
<tr bgcolor="#{BGCOLOR}">
|
584
|
+
<th align="left" colspan='#{@inherit_box.is_checked? ? '3' : '3'}'><font size="+2">
|
585
|
+
<b>#{caption}</b></font></th>
|
586
|
+
#{"<tr bgcolor='#ccccff'><th colspan='2'></th><th>Inherited from</th>" if @inherit_box.is_checked?}
|
587
|
+
</tr>
|
588
|
+
#{html}
|
589
|
+
] : ''
|
590
|
+
end
|
591
|
+
|
592
|
+
def html_qt_marker(s)
|
593
|
+
html=escape(s)
|
594
|
+
begin
|
595
|
+
if s.start_with?('Qt::') && s!=@rubyname && s!=(@rubyname+'[]') &&
|
596
|
+
(klass=eval(s.gsub('[]',''))) && (klass.respond_to? :java_class)
|
597
|
+
url=klass.java_class.name.gsub('.','/').gsub('$','.')
|
598
|
+
html="<a href='#{url}'>" + escape(s) + "</a>"
|
599
|
+
end
|
600
|
+
rescue
|
601
|
+
end
|
602
|
+
|
603
|
+
return html
|
604
|
+
end
|
605
|
+
|
606
|
+
def html_signals
|
607
|
+
html=''
|
608
|
+
@fields.sort{ |a,b| a.methodname <=> b.methodname}.each{ |method|
|
609
|
+
n=method.returntype[-1,1]
|
610
|
+
#emitargs=escape(@signals[method.methodname+n] || '')
|
611
|
+
emitargs= if (x=@signals[method.methodname+n]) && (x.size>0)
|
612
|
+
'(' + x[1..-2].
|
613
|
+
split(',').
|
614
|
+
map{ |arg| html_qt_marker(arg)}.
|
615
|
+
join(', ') +
|
616
|
+
')'
|
617
|
+
else
|
618
|
+
'()'
|
619
|
+
end
|
620
|
+
html+=%Q[<tr><td>#{html_qt_marker(method.returntype)}</td>
|
621
|
+
<td>#{escape(method.methodname)}</td
|
622
|
+
><td bgcolor="yellow"><i>slot</i>#{emitargs}</td></tr>]
|
623
|
+
}
|
624
|
+
return (html.size>0) ? %Q[
|
625
|
+
<!-- =========================== Signals =========================== -->
|
626
|
+
<tr bgcolor="#{BGCOLOR}">
|
627
|
+
<th align="left" colspan="3"><font size="+2">
|
628
|
+
<b>Signals</b></font></th>
|
629
|
+
</tr>
|
630
|
+
<font size='-1'><tr><th colspan="2">signalemitter</th><th>connects to</th>
|
631
|
+
#{html}
|
632
|
+
] : ''
|
633
|
+
end
|
634
|
+
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
class MethodAttributes
|
639
|
+
attr_accessor :returntype,:methodname,:receiver,:argtypes,:lang,:prefixes,:annotations
|
640
|
+
#call with either signature string OR
|
641
|
+
#Java::JavaLangReflect::Method OR
|
642
|
+
#Java::JavaLangReflect::Field OR
|
643
|
+
#Java::JavaConstructor
|
644
|
+
def self.namespace(&block)
|
645
|
+
@@block=block
|
646
|
+
end
|
647
|
+
|
648
|
+
def initialize(javamethod)
|
649
|
+
#public final com.trolltech.qt.core.QPoint com.trolltech.qt.gui.QWidget.mapFrom(com.trolltech.qt.gui.QWidget,com.trolltech.qt.core.QPoint)
|
650
|
+
#public final com.trolltech.qt.core.QSize com.trolltech.qt.gui.QAbstractButton.iconSize()
|
651
|
+
#public final com.trolltech.qt.core.Qt$LayoutDirection com.trolltech.qt.gui.QWidget.layoutDirection()
|
652
|
+
#public final void com.trolltech.qt.gui.QWidget.grabMouse(com.trolltech.qt.gui.QCursor)
|
653
|
+
#protected void com.trolltech.qt.gui.QWidget.dragLeaveEvent(com.trolltech.qt.gui.QDragLeaveEvent)
|
654
|
+
@annotations=nil
|
655
|
+
if javamethod.respond_to?(:annotations)
|
656
|
+
@annotations=javamethod.annotations.
|
657
|
+
map{ |ann| ann.to_java(java.lang.annotation.Annotation).toString}.
|
658
|
+
join(',').
|
659
|
+
gsub!('@com.trolltech.qt.','')
|
660
|
+
end
|
661
|
+
method=@@block.call("#{javamethod}")
|
662
|
+
elems=method.split('(')
|
663
|
+
@prefixes=elems[0].split(' ')
|
664
|
+
@returntype=@prefixes[-2]
|
665
|
+
#@returntype=@prefixes[0..-2]
|
666
|
+
@methodname=@prefixes[-1].split('.')[-1]
|
667
|
+
@receiver=@prefixes[-1].split('.')[-2]
|
668
|
+
@argtypes=(elems.size>1) ? (elems[1].split(')')[0] || '') : ''
|
669
|
+
@argtypes='('+@argtypes+')' if @argtypes.size>0
|
670
|
+
@lang=:java
|
671
|
+
end
|
672
|
+
def ruby?
|
673
|
+
@lang==:ruby
|
674
|
+
end
|
675
|
+
#
|
676
|
+
# .final, public, private, protected, abstract
|
677
|
+
# .public?
|
678
|
+
def method_missing(symbol,*args)
|
679
|
+
m=symbol.to_s[0...-1]
|
680
|
+
return @prefixes.include?(m)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
class PackageModel < Qt::AbstractListModel
|
685
|
+
|
686
|
+
def initialize(packagetree)
|
687
|
+
@package_tree=packagetree
|
688
|
+
@packages=packagetree.keys & Qt::Internal::Packages
|
689
|
+
super()
|
690
|
+
end
|
691
|
+
|
692
|
+
def classes(index)
|
693
|
+
package_name=@packages[index.row]
|
694
|
+
@package_tree[package_name].classes
|
695
|
+
end
|
696
|
+
|
697
|
+
|
698
|
+
def data(index, role=Qt::DisplayRole)
|
699
|
+
return @packages[index.row] if role == Qt::DisplayRole
|
700
|
+
return Qt::Variant.new
|
701
|
+
end
|
702
|
+
|
703
|
+
def rowCount(parent)
|
704
|
+
return @packages.size
|
705
|
+
end
|
706
|
+
|
707
|
+
end
|
708
|
+
|
709
|
+
#Qt::AbstractListModel specialized for acess to Classes
|
710
|
+
#supports active filter (respons while you type)
|
711
|
+
#filter_edit is Qt::LineEdit monitored for text changes, otherwise managed outside this class
|
712
|
+
#data supports Qt::UserRole (returns Class name as String)
|
713
|
+
# and Qt::DisplayRole (returns Class name as HTML with tagged filter matches)
|
714
|
+
class FilteredClassModel < Qt::AbstractListModel
|
715
|
+
|
716
|
+
def initialize(filter_edit,max_cache=20)
|
717
|
+
super()
|
718
|
+
@filter_edit=filter_edit
|
719
|
+
@max_cache=max_cache
|
720
|
+
@classes=[]
|
721
|
+
@row2rec=[]
|
722
|
+
reset_filter
|
723
|
+
@filter_edit.textChanged.connect{ |s| filter_changed(s)}
|
724
|
+
end
|
725
|
+
|
726
|
+
def classes=(cl)
|
727
|
+
@classes=cl
|
728
|
+
@row2rec=(0...@classes.size).to_a
|
729
|
+
reset_filter
|
730
|
+
end
|
731
|
+
|
732
|
+
def data(index, role=Qt::DisplayRole)
|
733
|
+
return Qt::Variant.new if index.row >= @row2rec.size
|
734
|
+
return @classes[@row2rec[index.row]] if role == Qt::UserRole
|
735
|
+
return Qt::Variant.new unless role == Qt::DisplayRole
|
736
|
+
s=@classes[@row2rec[index.row]].gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
737
|
+
s=s.gsub(@rx){ |m| %Q[<span class="hilite">#{m}</span>]} if @filter.size>0
|
738
|
+
return s
|
739
|
+
|
740
|
+
end
|
741
|
+
|
742
|
+
def rowCount(parent)
|
743
|
+
return @row2rec.size
|
744
|
+
end
|
745
|
+
|
746
|
+
private
|
747
|
+
|
748
|
+
def reset_filter
|
749
|
+
@filter=''
|
750
|
+
@history={}
|
751
|
+
@cache={}
|
752
|
+
@filter_edit.text=''
|
753
|
+
end
|
754
|
+
|
755
|
+
def filter_changed(str)
|
756
|
+
s=str.downcase
|
757
|
+
@rx=Regexp.new(Regexp.escape(s),Regexp::IGNORECASE)
|
758
|
+
if s==''
|
759
|
+
row2rec=(0...@classes.size).to_a
|
760
|
+
elsif row2rec=@cache[s]
|
761
|
+
elsif (s.size>1) && (s[0...-1]==@filter)
|
762
|
+
row2rec=@row2rec.select{ |i| @classes[i] =~ @rx}
|
763
|
+
else
|
764
|
+
row2rec=(0...@classes.size).select{ |i| @classes[i] =~ @rx}
|
765
|
+
end
|
766
|
+
@filter=s
|
767
|
+
layout_about_to_be_changed.emit
|
768
|
+
@row2rec=row2rec
|
769
|
+
layout_changed.emit
|
770
|
+
@cache[s]=row2rec
|
771
|
+
@history[s]=Time.now
|
772
|
+
if @history.size > @max_cache
|
773
|
+
s=@history.sort{ |a,b| a[1] <=> b[1]}.first.first
|
774
|
+
@cache.delete(s)
|
775
|
+
@history.delete(s)
|
776
|
+
end
|
777
|
+
|
778
|
+
end
|
779
|
+
|
780
|
+
end
|
781
|
+
# simple class to assist fwd/bwd navigation
|
782
|
+
# left/right are Qt widgets than can be enabled/disabled and clicked
|
783
|
+
#push: add a page to the end of the queue
|
784
|
+
#browse: passes a call-back block to display a page
|
785
|
+
class Navigator
|
786
|
+
def initialize
|
787
|
+
@browse=nil
|
788
|
+
@left=nil
|
789
|
+
@right=nil
|
790
|
+
@index=0
|
791
|
+
@history=[]
|
792
|
+
end
|
793
|
+
def buttons(left,right)
|
794
|
+
@left=left
|
795
|
+
@left.enabled=false
|
796
|
+
@left.clicked.connect{move(-1)}
|
797
|
+
@right=right
|
798
|
+
@right.enabled=false
|
799
|
+
@right.clicked.connect{move(1)}
|
800
|
+
end
|
801
|
+
|
802
|
+
def push(page)
|
803
|
+
@history << page
|
804
|
+
@index=-1
|
805
|
+
@left.enabled=(@history.size>1)
|
806
|
+
@right.enabled=false
|
807
|
+
end
|
808
|
+
|
809
|
+
def browse(&block)
|
810
|
+
@browse=block
|
811
|
+
end
|
812
|
+
|
813
|
+
private
|
814
|
+
|
815
|
+
def move(delta)
|
816
|
+
return if (@index+delta >=0) || (@history.size+@index+delta<0)
|
817
|
+
@index+=delta
|
818
|
+
@browse.call(@history[@index]) if @browse
|
819
|
+
@left.enabled=(@history.size+@index>0)
|
820
|
+
@right.enabled=(@index<-1)
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
app=Qt::Application.new(ARGV)
|
825
|
+
classexplorer=ClassExplorer.new
|
826
|
+
classexplorer.show
|
827
|
+
app.exec
|
828
|
+
|