1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 ** * Redistributions of source code must retain the above copyright
25 ** notice, this list of conditions and the following disclaimer.
26 ** * Redistributions in binary form must reproduce the above copyright
27 ** notice, this list of conditions and the following disclaimer in
28 ** the documentation and/or other materials provided with the
29 ** distribution.
30 ** * Neither the name of The Qt Company Ltd nor the names of its
31 ** contributors may be used to endorse or promote products derived
32 ** from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50
51 #include "glwidget.h"
52 #include <QPainter>
53 #include <QPaintEngine>
54 #include <QOpenGLShaderProgram>
55 #include <QOpenGLTexture>
56 #include <QRandomGenerator>
57 #include <QCoreApplication>
58 #include <qmath.h>
59
60 #include "mainwindow.h"
61 #include "bubble.h"
62
63 const int bubbleNum = 8;
64
65 #ifndef GL_SRGB8_ALPHA8
66 #define GL_SRGB8_ALPHA8 0x8C43
67 #endif
68
GLWidget(MainWindow * mw,bool button,const QColor & background)69 GLWidget::GLWidget(MainWindow *mw, bool button, const QColor &background)
70 : m_mainWindow(mw),
71 m_showBubbles(true),
72 m_qtLogo(true),
73 m_frames(0),
74 m_program1(0),
75 m_program2(0),
76 m_texture(0),
77 m_transparent(false),
78 m_btn(0),
79 m_hasButton(button),
80 m_background(background)
81 {
82 setMinimumSize(300, 250);
83 if (QCoreApplication::arguments().contains(QStringLiteral("--srgb")))
84 setTextureFormat(GL_SRGB8_ALPHA8);
85 }
86
~GLWidget()87 GLWidget::~GLWidget()
88 {
89 qDeleteAll(m_bubbles);
90
91 // And now release all OpenGL resources.
92 makeCurrent();
93 delete m_texture;
94 delete m_program1;
95 delete m_program2;
96 delete m_vshader1;
97 delete m_fshader1;
98 delete m_vshader2;
99 delete m_fshader2;
100 m_vbo1.destroy();
101 m_vbo2.destroy();
102 doneCurrent();
103 }
104
setScaling(int scale)105 void GLWidget::setScaling(int scale)
106 {
107 if (scale > 30)
108 m_fScale = 1 + qreal(scale - 30) / 30 * 0.25;
109 else if (scale < 30)
110 m_fScale = 1 - (qreal(30 - scale) / 30 * 0.25);
111 else
112 m_fScale = 1;
113 }
114
setLogo()115 void GLWidget::setLogo()
116 {
117 m_qtLogo = true;
118 }
119
setTexture()120 void GLWidget::setTexture()
121 {
122 m_qtLogo = false;
123 }
124
setShowBubbles(bool bubbles)125 void GLWidget::setShowBubbles(bool bubbles)
126 {
127 m_showBubbles = bubbles;
128 }
129
paintQtLogo()130 void GLWidget::paintQtLogo()
131 {
132 m_program1->enableAttributeArray(m_vertexAttr1);
133 m_program1->enableAttributeArray(m_normalAttr1);
134
135 m_vbo1.bind();
136 // The data in the buffer is placed like this:
137 // vertex1.x, vertex1.y, vertex1.z, normal1.x, normal1.y, normal1.z, vertex2.x, ...
138 m_program1->setAttributeBuffer(m_vertexAttr1, GL_FLOAT, 0, 3, 6 * sizeof(GLfloat));
139 m_program1->setAttributeBuffer(m_normalAttr1, GL_FLOAT, 3 * sizeof(GLfloat), 3, 6 * sizeof(GLfloat));
140 m_vbo1.release();
141
142 glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
143
144 m_program1->disableAttributeArray(m_normalAttr1);
145 m_program1->disableAttributeArray(m_vertexAttr1);
146 }
147
paintTexturedCube()148 void GLWidget::paintTexturedCube()
149 {
150 m_texture->bind();
151
152 if (!m_vbo2.isCreated()) {
153 static GLfloat afVertices[] = {
154 -0.5, 0.5, 0.5, 0.5,-0.5,0.5,-0.5,-0.5,0.5,
155 0.5, -0.5, 0.5, -0.5,0.5,0.5,0.5,0.5,0.5,
156 -0.5, -0.5, -0.5, 0.5,-0.5,-0.5,-0.5,0.5,-0.5,
157 0.5, 0.5, -0.5, -0.5,0.5,-0.5,0.5,-0.5,-0.5,
158
159 0.5, -0.5, -0.5, 0.5,-0.5,0.5,0.5,0.5,-0.5,
160 0.5, 0.5, 0.5, 0.5,0.5,-0.5,0.5,-0.5,0.5,
161 -0.5, 0.5, -0.5, -0.5,-0.5,0.5,-0.5,-0.5,-0.5,
162 -0.5, -0.5, 0.5, -0.5,0.5,-0.5,-0.5,0.5,0.5,
163
164 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
165 -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
166 -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
167 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5
168 };
169
170 static GLfloat afTexCoord[] = {
171 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
172 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
173 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
174 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
175
176 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
177 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
178 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
179 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
180
181 0.0f,1.0f, 1.0f,0.0f, 1.0f,1.0f,
182 1.0f,0.0f, 0.0f,1.0f, 0.0f,0.0f,
183 1.0f,0.0f, 1.0f,1.0f, 0.0f,0.0f,
184 0.0f,1.0f, 0.0f,0.0f, 1.0f,1.0f
185 };
186
187 GLfloat afNormals[] = {
188
189 0,0,-1, 0,0,-1, 0,0,-1,
190 0,0,-1, 0,0,-1, 0,0,-1,
191 0,0,1, 0,0,1, 0,0,1,
192 0,0,1, 0,0,1, 0,0,1,
193
194 -1,0,0, -1,0,0, -1,0,0,
195 -1,0,0, -1,0,0, -1,0,0,
196 1,0,0, 1,0,0, 1,0,0,
197 1,0,0, 1,0,0, 1,0,0,
198
199 0,-1,0, 0,-1,0, 0,-1,0,
200 0,-1,0, 0,-1,0, 0,-1,0,
201 0,1,0, 0,1,0, 0,1,0,
202 0,1,0, 0,1,0, 0,1,0
203 };
204
205 m_vbo2.create();
206 m_vbo2.bind();
207 m_vbo2.allocate(36 * 8 * sizeof(GLfloat));
208 m_vbo2.write(0, afVertices, sizeof(afVertices));
209 m_vbo2.write(sizeof(afVertices), afTexCoord, sizeof(afTexCoord));
210 m_vbo2.write(sizeof(afVertices) + sizeof(afTexCoord), afNormals, sizeof(afNormals));
211 m_vbo2.release();
212 }
213
214 m_program2->setUniformValue(m_textureUniform2, 0); // use texture unit 0
215
216 m_program2->enableAttributeArray(m_vertexAttr2);
217 m_program2->enableAttributeArray(m_normalAttr2);
218 m_program2->enableAttributeArray(m_texCoordAttr2);
219
220 m_vbo2.bind();
221 // In the buffer we first have 36 vertices (3 floats for each), then 36 texture
222 // coordinates (2 floats for each), then 36 normals (3 floats for each).
223 m_program2->setAttributeBuffer(m_vertexAttr2, GL_FLOAT, 0, 3);
224 m_program2->setAttributeBuffer(m_texCoordAttr2, GL_FLOAT, 36 * 3 * sizeof(GLfloat), 2);
225 m_program2->setAttributeBuffer(m_normalAttr2, GL_FLOAT, 36 * 5 * sizeof(GLfloat), 3);
226 m_vbo2.release();
227
228 glDrawArrays(GL_TRIANGLES, 0, 36);
229
230 m_program2->disableAttributeArray(m_vertexAttr2);
231 m_program2->disableAttributeArray(m_normalAttr2);
232 m_program2->disableAttributeArray(m_texCoordAttr2);
233 }
234
initializeGL()235 void GLWidget::initializeGL()
236 {
237 initializeOpenGLFunctions();
238
239 m_texture = new QOpenGLTexture(QImage(":/qt.png"));
240
241 m_vshader1 = new QOpenGLShader(QOpenGLShader::Vertex);
242 const char *vsrc1 =
243 "attribute highp vec4 vertex;\n"
244 "attribute mediump vec3 normal;\n"
245 "uniform mediump mat4 matrix;\n"
246 "varying mediump vec4 color;\n"
247 "void main(void)\n"
248 "{\n"
249 " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
250 " float angle = max(dot(normal, toLight), 0.0);\n"
251 " vec3 col = vec3(0.40, 1.0, 0.0);\n"
252 " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);\n"
253 " color = clamp(color, 0.0, 1.0);\n"
254 " gl_Position = matrix * vertex;\n"
255 "}\n";
256 m_vshader1->compileSourceCode(vsrc1);
257
258 m_fshader1 = new QOpenGLShader(QOpenGLShader::Fragment);
259 const char *fsrc1 =
260 "varying mediump vec4 color;\n"
261 "void main(void)\n"
262 "{\n"
263 " gl_FragColor = color;\n"
264 "}\n";
265 m_fshader1->compileSourceCode(fsrc1);
266
267 m_program1 = new QOpenGLShaderProgram;
268 m_program1->addShader(m_vshader1);
269 m_program1->addShader(m_fshader1);
270 m_program1->link();
271
272 m_vertexAttr1 = m_program1->attributeLocation("vertex");
273 m_normalAttr1 = m_program1->attributeLocation("normal");
274 m_matrixUniform1 = m_program1->uniformLocation("matrix");
275
276 m_vshader2 = new QOpenGLShader(QOpenGLShader::Vertex);
277 const char *vsrc2 =
278 "attribute highp vec4 vertex;\n"
279 "attribute highp vec4 texCoord;\n"
280 "attribute mediump vec3 normal;\n"
281 "uniform mediump mat4 matrix;\n"
282 "varying highp vec4 texc;\n"
283 "varying mediump float angle;\n"
284 "void main(void)\n"
285 "{\n"
286 " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
287 " angle = max(dot(normal, toLight), 0.0);\n"
288 " gl_Position = matrix * vertex;\n"
289 " texc = texCoord;\n"
290 "}\n";
291 m_vshader2->compileSourceCode(vsrc2);
292
293 m_fshader2 = new QOpenGLShader(QOpenGLShader::Fragment);
294 const char *fsrc2 =
295 "varying highp vec4 texc;\n"
296 "uniform sampler2D tex;\n"
297 "varying mediump float angle;\n"
298 "void main(void)\n"
299 "{\n"
300 " highp vec3 color = texture2D(tex, texc.st).rgb;\n"
301 " color = color * 0.2 + color * 0.8 * angle;\n"
302 " gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n"
303 "}\n";
304 m_fshader2->compileSourceCode(fsrc2);
305
306 m_program2 = new QOpenGLShaderProgram;
307 m_program2->addShader(m_vshader2);
308 m_program2->addShader(m_fshader2);
309 m_program2->link();
310
311 m_vertexAttr2 = m_program2->attributeLocation("vertex");
312 m_normalAttr2 = m_program2->attributeLocation("normal");
313 m_texCoordAttr2 = m_program2->attributeLocation("texCoord");
314 m_matrixUniform2 = m_program2->uniformLocation("matrix");
315 m_textureUniform2 = m_program2->uniformLocation("tex");
316
317 m_fAngle = 0;
318 m_fScale = 1;
319
320 createGeometry();
321
322 // Use a vertex buffer object. Client-side pointers are old-school and should be avoided.
323 m_vbo1.create();
324 m_vbo1.bind();
325 // For the cube all the data belonging to the texture coordinates and
326 // normals is placed separately, after the vertices. Here, for the Qt logo,
327 // let's do something different and potentially more efficient: create a
328 // properly interleaved data set.
329 const int vertexCount = m_vertices.count();
330 QVector<GLfloat> buf;
331 buf.resize(vertexCount * 3 * 2);
332 GLfloat *p = buf.data();
333 for (int i = 0; i < vertexCount; ++i) {
334 *p++ = m_vertices[i].x();
335 *p++ = m_vertices[i].y();
336 *p++ = m_vertices[i].z();
337 *p++ = m_normals[i].x();
338 *p++ = m_normals[i].y();
339 *p++ = m_normals[i].z();
340 }
341 m_vbo1.allocate(buf.constData(), buf.count() * sizeof(GLfloat));
342 m_vbo1.release();
343
344 createBubbles(bubbleNum - m_bubbles.count());
345 }
346
paintGL()347 void GLWidget::paintGL()
348 {
349 createBubbles(bubbleNum - m_bubbles.count());
350
351 QPainter painter;
352 painter.begin(this);
353
354 painter.beginNativePainting();
355
356 glClearColor(m_background.redF(), m_background.greenF(), m_background.blueF(), m_transparent ? 0.0f : 1.0f);
357 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
358
359 glFrontFace(GL_CW);
360 glCullFace(GL_FRONT);
361 glEnable(GL_CULL_FACE);
362 glEnable(GL_DEPTH_TEST);
363
364 QMatrix4x4 modelview;
365 modelview.rotate(m_fAngle, 0.0f, 1.0f, 0.0f);
366 modelview.rotate(m_fAngle, 1.0f, 0.0f, 0.0f);
367 modelview.rotate(m_fAngle, 0.0f, 0.0f, 1.0f);
368 modelview.scale(m_fScale);
369 modelview.translate(0.0f, -0.2f, 0.0f);
370
371 if (m_qtLogo) {
372 m_program1->bind();
373 m_program1->setUniformValue(m_matrixUniform1, modelview);
374 paintQtLogo();
375 m_program1->release();
376 } else {
377 m_program2->bind();
378 m_program2->setUniformValue(m_matrixUniform2, modelview);
379 paintTexturedCube();
380 m_program2->release();
381 }
382
383 glDisable(GL_DEPTH_TEST);
384 glDisable(GL_CULL_FACE);
385
386 painter.endNativePainting();
387
388 if (m_showBubbles)
389 foreach (Bubble *bubble, m_bubbles) {
390 bubble->drawBubble(&painter);
391 }
392
393 if (const int elapsed = m_time.elapsed()) {
394 QString framesPerSecond;
395 framesPerSecond.setNum(m_frames /(elapsed / 1000.0), 'f', 2);
396 painter.setPen(m_transparent ? Qt::black : Qt::white);
397 painter.drawText(20, 40, framesPerSecond + " paintGL calls / s");
398 }
399
400 painter.end();
401
402 QMutableListIterator<Bubble*> iter(m_bubbles);
403
404 while (iter.hasNext()) {
405 Bubble *bubble = iter.next();
406 bubble->move(rect());
407 }
408 if (!(m_frames % 100)) {
409 m_time.start();
410 m_frames = 0;
411 }
412 m_fAngle += 1.0f;
413 ++m_frames;
414
415 // When requested, follow the ideal way to animate: Rely on
416 // blocking swap and just schedule updates continuously.
417 if (!m_mainWindow->timerEnabled())
418 update();
419 }
420
createBubbles(int number)421 void GLWidget::createBubbles(int number)
422 {
423 for (int i = 0; i < number; ++i) {
424 QPointF position(width()*(0.1 + QRandomGenerator::global()->bounded(0.8)),
425 height()*(0.1 + QRandomGenerator::global()->bounded(0.8)));
426 qreal radius = qMin(width(), height())*(0.0175 + QRandomGenerator::global()->bounded(0.0875));
427 QPointF velocity(width()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(1.0)),
428 height()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(1.0)));
429
430 m_bubbles.append(new Bubble(position, radius, velocity));
431 }
432 }
433
createGeometry()434 void GLWidget::createGeometry()
435 {
436 m_vertices.clear();
437 m_normals.clear();
438
439 qreal x1 = +0.06f;
440 qreal y1 = -0.14f;
441 qreal x2 = +0.14f;
442 qreal y2 = -0.06f;
443 qreal x3 = +0.08f;
444 qreal y3 = +0.00f;
445 qreal x4 = +0.30f;
446 qreal y4 = +0.22f;
447
448 quad(x1, y1, x2, y2, y2, x2, y1, x1);
449 quad(x3, y3, x4, y4, y4, x4, y3, x3);
450
451 extrude(x1, y1, x2, y2);
452 extrude(x2, y2, y2, x2);
453 extrude(y2, x2, y1, x1);
454 extrude(y1, x1, x1, y1);
455 extrude(x3, y3, x4, y4);
456 extrude(x4, y4, y4, x4);
457 extrude(y4, x4, y3, x3);
458
459 const int NumSectors = 100;
460 const qreal sectorAngle = 2 * qreal(M_PI) / NumSectors;
461
462 for (int i = 0; i < NumSectors; ++i) {
463 qreal angle = i * sectorAngle;
464 qreal x5 = 0.30 * sin(angle);
465 qreal y5 = 0.30 * cos(angle);
466 qreal x6 = 0.20 * sin(angle);
467 qreal y6 = 0.20 * cos(angle);
468
469 angle += sectorAngle;
470 qreal x7 = 0.20 * sin(angle);
471 qreal y7 = 0.20 * cos(angle);
472 qreal x8 = 0.30 * sin(angle);
473 qreal y8 = 0.30 * cos(angle);
474
475 quad(x5, y5, x6, y6, x7, y7, x8, y8);
476
477 extrude(x6, y6, x7, y7);
478 extrude(x8, y8, x5, y5);
479 }
480
481 for (int i = 0;i < m_vertices.size();i++)
482 m_vertices[i] *= 2.0f;
483 }
484
quad(qreal x1,qreal y1,qreal x2,qreal y2,qreal x3,qreal y3,qreal x4,qreal y4)485 void GLWidget::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4)
486 {
487 m_vertices << QVector3D(x1, y1, -0.05f);
488 m_vertices << QVector3D(x2, y2, -0.05f);
489 m_vertices << QVector3D(x4, y4, -0.05f);
490
491 m_vertices << QVector3D(x3, y3, -0.05f);
492 m_vertices << QVector3D(x4, y4, -0.05f);
493 m_vertices << QVector3D(x2, y2, -0.05f);
494
495 QVector3D n = QVector3D::normal
496 (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(x4 - x1, y4 - y1, 0.0f));
497
498 m_normals << n;
499 m_normals << n;
500 m_normals << n;
501
502 m_normals << n;
503 m_normals << n;
504 m_normals << n;
505
506 m_vertices << QVector3D(x4, y4, 0.05f);
507 m_vertices << QVector3D(x2, y2, 0.05f);
508 m_vertices << QVector3D(x1, y1, 0.05f);
509
510 m_vertices << QVector3D(x2, y2, 0.05f);
511 m_vertices << QVector3D(x4, y4, 0.05f);
512 m_vertices << QVector3D(x3, y3, 0.05f);
513
514 n = QVector3D::normal
515 (QVector3D(x2 - x4, y2 - y4, 0.0f), QVector3D(x1 - x4, y1 - y4, 0.0f));
516
517 m_normals << n;
518 m_normals << n;
519 m_normals << n;
520
521 m_normals << n;
522 m_normals << n;
523 m_normals << n;
524 }
525
extrude(qreal x1,qreal y1,qreal x2,qreal y2)526 void GLWidget::extrude(qreal x1, qreal y1, qreal x2, qreal y2)
527 {
528 m_vertices << QVector3D(x1, y1, +0.05f);
529 m_vertices << QVector3D(x2, y2, +0.05f);
530 m_vertices << QVector3D(x1, y1, -0.05f);
531
532 m_vertices << QVector3D(x2, y2, -0.05f);
533 m_vertices << QVector3D(x1, y1, -0.05f);
534 m_vertices << QVector3D(x2, y2, +0.05f);
535
536 QVector3D n = QVector3D::normal
537 (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(0.0f, 0.0f, -0.1f));
538
539 m_normals << n;
540 m_normals << n;
541 m_normals << n;
542
543 m_normals << n;
544 m_normals << n;
545 m_normals << n;
546 }
547
setTransparent(bool transparent)548 void GLWidget::setTransparent(bool transparent)
549 {
550 setAttribute(Qt::WA_AlwaysStackOnTop, transparent);
551 m_transparent = transparent;
552 // Call update() on the top-level window after toggling AlwayStackOnTop to make sure
553 // the entire backingstore is updated accordingly.
554 window()->update();
555 }
556
resizeGL(int,int)557 void GLWidget::resizeGL(int, int)
558 {
559 if (m_hasButton) {
560 if (!m_btn) {
561 m_btn = new QPushButton("A widget on top.\nPress for more widgets.", this);
562 connect(m_btn, &QPushButton::clicked, this, &GLWidget::handleButtonPress);
563 }
564 m_btn->move(20, 80);
565 }
566 }
567
handleButtonPress()568 void GLWidget::handleButtonPress()
569 {
570 m_mainWindow->addNew();
571 }
572