qt_connect 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 &copy; 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
+
@@ -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('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
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('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
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
+