8 #include <QtopiaApplication>
9 #include <QSoftMenuBar>
21 // stubs to allow *not* supporting printing
23 void document_add_puzzle(document
*doc
, const game
*game
, game_params
*par
,
24 game_state
*st
, game_state
*st2
)
29 /* ----------------------------------------------------------------------
30 * Error reporting functions used elsewhere.
33 void fatal(char *fmt
, ...)
37 fprintf(stderr
, "fatal error: ");
40 vfprintf(stderr
, fmt
, ap
);
43 fprintf(stderr
, "\n");
47 /* ----------------------------------------------------------------------
48 * Qt front end to puzzles.
56 static void do_start_draw(frontend
*fe
)
58 bool ok
= fe
->painter
->begin(fe
->pixmap
);
59 fe
->painter
->setRenderHints(QPainter::Antialiasing
, true);
62 static void do_end_draw(frontend
*fe
)
64 bool ok
= fe
->painter
->end();
68 static void setup_backing_store(frontend
*fe
)
70 fe
->pixmap
= new QPixmap(fe
->pw
, fe
->ph
);
73 fe
->painter
->eraseRect(0, 0, fe
->pw
, fe
->ph
);
77 static int backing_store_ok(frontend
*fe
)
79 return (!!fe
->pixmap
);
82 static void teardown_backing_store(frontend
*fe
)
88 static void repaint_rectangle(frontend
*fe
, QPaintDevice
*widget
,
89 int x
, int y
, int w
, int h
)
91 QPainter
painter(widget
);
93 painter
.eraseRect(x
, y
, fe
->ox
- x
, h
);
98 painter
.eraseRect(x
, y
, w
, fe
->oy
- y
);
103 painter
.eraseRect(x
+ fe
->pw
, y
, w
- fe
->pw
, h
);
107 painter
.eraseRect(x
, y
+ fe
->ph
, w
, h
- fe
->ph
);
110 painter
.drawPixmap(QPoint(x
, y
), *fe
->pixmap
,
111 QRect(x
- fe
->ox
, y
- fe
->oy
, w
, h
));
114 Canvas::Canvas(frontend
* fe
): QWidget(), fe(fe
) { }
115 void Canvas::paintEvent (QPaintEvent
*event
)
117 const QRect
& rect
= event
->rect();
118 repaint_rectangle(fe
, fe
->area
,
120 rect
.width(), rect
.height());
123 void Canvas::do_resize(const QSize
& size
)
127 if (backing_store_ok(fe
))
128 teardown_backing_store(fe
);
130 w
= fe
->w
= size
.width();
131 h
= fe
->h
= size
.height();
132 midend_size(fe
->me
, &w
, &h
, TRUE
);
135 fe
->ox
= (fe
->w
- fe
->pw
) / 2;
136 fe
->oy
= (fe
->h
- fe
->ph
) / 2;
138 setup_backing_store(fe
);
139 midend_force_redraw(fe
->me
);
141 void Canvas::resizeEvent (QResizeEvent
*event
)
143 do_resize(event
->size());
146 void Canvas::mousePressEvent (QMouseEvent
*event
)
149 if (!backing_store_ok(fe
))
151 switch(event
->button()) {
152 case Qt::LeftButton
: button
= LEFT_BUTTON
; break;
153 case Qt::RightButton
: button
= RIGHT_BUTTON
; break;
154 case Qt::MidButton
: button
= MIDDLE_BUTTON
; break;
155 default: event
->ignore(); return; /* don't even know what button! */
158 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
159 event
->y() - fe
->oy
, button
))
162 void Canvas::mouseReleaseEvent (QMouseEvent
*event
)
165 if (!backing_store_ok(fe
))
167 switch(event
->button()) {
168 case Qt::LeftButton
: button
= LEFT_RELEASE
; break;
169 case Qt::RightButton
: button
= RIGHT_RELEASE
; break;
170 case Qt::MidButton
: button
= MIDDLE_RELEASE
; break;
171 default: event
->ignore(); return; /* don't even know what button! */
174 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
175 event
->y() - fe
->oy
, button
))
178 void Canvas::mouseMoveEvent (QMouseEvent
*event
)
181 Qt::MouseButtons buttons
= event
->buttons();
182 if (buttons
.testFlag(Qt::MidButton
))
183 button
= MIDDLE_DRAG
;
184 else if (buttons
.testFlag(Qt::LeftButton
))
186 else if (buttons
.testFlag(Qt::RightButton
))
189 event
->ignore(); /* don't even know what button! */
193 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
194 event
->y() - fe
->oy
, button
))
199 void get_random_seed(void **randseed
, int *randseedsize
)
201 struct timeval
*tvp
= snew(struct timeval
);
202 gettimeofday(tvp
, NULL
);
203 *randseed
= (void *)tvp
;
204 *randseedsize
= sizeof(struct timeval
);
207 void frontend_default_colour(frontend
*fe
, float *output
)
209 // FIXME: should probably ask fe->area, but it looks unavailable
211 // FIXME: that is *not* white !?
217 static void snaffle_colours(frontend
*fe
)
219 const float* colours
= midend_colours(fe
->me
, &fe
->ncolours
);
220 fe
->colours
= new QColor
[fe
->ncolours
];
221 for (int i
= 0; i
< fe
->ncolours
; i
+= 1)
222 fe
->colours
[i
].setRgbF(colours
[3*i
], colours
[3*i
+ 1], colours
[3*i
+ 2]);
227 class Timer
: public QTimer
231 struct timeval last_time
;
232 Timer(frontend
* fe
): QTimer() {
236 void timerEvent(QTimerEvent
*e
) {
239 gettimeofday(&now
, NULL
);
240 elapsed
= ((now
.tv_usec
- last_time
.tv_usec
) * 0.000001F
+
241 (now
.tv_sec
- last_time
.tv_sec
));
242 midend_timer(fe
->me
, elapsed
); /* may clear timer_active */
247 void deactivate_timer(frontend
*fe
)
250 return; /* can happen due to --generate */
254 void activate_timer(frontend
*fe
)
257 return; /* can happen due to --generate */
258 if (!fe
->timer
->isActive()) {
259 fe
->timer
->start(20);
260 gettimeofday(&fe
->timer
->last_time
, NULL
);
266 static void qt_start_draw(void *handle
)
268 frontend
*fe
= (frontend
*)handle
;
276 static void qt_end_draw(void *handle
)
278 frontend
*fe
= (frontend
*)handle
;
281 if (fe
->bbox_l
< fe
->bbox_r
&& fe
->bbox_u
< fe
->bbox_d
) {
282 fe
->area
->update(fe
->bbox_l
- 1 + fe
->ox
,
283 fe
->bbox_u
- 1 + fe
->oy
,
284 fe
->bbox_r
- fe
->bbox_l
+ 2,
285 fe
->bbox_d
- fe
->bbox_u
+ 2);
289 static void qt_clip(void *handle
, int x
, int y
, int w
, int h
)
291 frontend
*fe
= (frontend
*)handle
;
292 fe
->painter
->setClipRect(x
, y
, w
, h
);
295 static void qt_unclip(void *handle
)
297 frontend
*fe
= (frontend
*)handle
;
298 fe
->painter
->setClipRect(0, 0, fe
->w
, fe
->h
);
301 static void qt_draw_text(void *handle
, int x
, int y
, int fonttype
, int fontsize
,
302 int align
, int colour
, char *text
)
304 frontend
*fe
= (frontend
*)handle
;
307 font
.setPixelSize(fontsize
);
308 if (fonttype
== FONT_FIXED
)
309 font
.setFamily("Monospace");
310 fe
->painter
->setFont(font
);
312 QFontMetrics fm
= fe
->painter
->fontMetrics();
313 int width
= fm
.width(text
);
314 QRect
rect(0, 0, width
, fontsize
);
317 if (align
& ALIGN_VCENTRE
) {
318 flags
|= Qt::AlignVCenter
;
319 rect
.moveTop(y
- fontsize
/2);
320 } else { // as if (align & ALIGN_VNORMAL)
321 // FIXME implemented as bottom, not baseline...
322 flags
|= Qt::AlignBottom
;
325 if (align
& ALIGN_HCENTRE
) {
326 flags
|= Qt::AlignHCenter
;
327 rect
.moveLeft(x
- width
/2);
328 } else if (align
& ALIGN_HRIGHT
) {
329 flags
|= Qt::AlignRight
;
331 } else { // as if (align & ALIGN_HLEFT)
332 flags
|= Qt::AlignLeft
;
336 fe
->painter
->setPen(fe
->colours
[colour
]);
337 fe
->painter
->drawText(rect
, flags
, text
);
340 static void qt_draw_rect(void *handle
, int x
, int y
, int w
, int h
, int colour
)
342 frontend
*fe
= (frontend
*)handle
;
343 fe
->painter
->fillRect(x
, y
, w
, h
, fe
->colours
[colour
]);
346 static void qt_draw_line(void *handle
, int x1
, int y1
, int x2
, int y2
, int colour
)
348 frontend
*fe
= (frontend
*)handle
;
349 fe
->painter
->setPen(fe
->colours
[colour
]);
350 fe
->painter
->drawLine(x1
, y1
, x2
, y2
);
353 static void qt_draw_polygon(void *handle
, int *coords
, int npoints
,
354 int fillcolour
, int outlinecolour
)
356 frontend
*fe
= (frontend
*)handle
;
357 const QBrush oldbrush
= fe
->painter
->brush();
358 if (fillcolour
!= -1)
359 fe
->painter
->setBrush(fe
->colours
[fillcolour
]);
361 for (int i
=0; i
< npoints
; i
++)
362 polygon
<< QPoint(coords
[i
*2], coords
[i
*2 + 1]);
363 fe
->painter
->setPen(fe
->colours
[outlinecolour
]);
364 fe
->painter
->drawPolygon(polygon
);
365 if (fillcolour
!= -1)
366 fe
->painter
->setBrush(oldbrush
);
369 static void qt_draw_circle(void *handle
, int cx
, int cy
, int radius
,
370 int fillcolour
, int outlinecolour
)
372 frontend
*fe
= (frontend
*)handle
;
374 path
.addEllipse(QPoint(cx
, cy
), radius
, radius
);
375 if (fillcolour
!= -1)
376 fe
->painter
->fillPath(path
, fe
->colours
[fillcolour
]);
377 fe
->painter
->setPen(fe
->colours
[outlinecolour
]);
378 fe
->painter
->drawPath(path
);
381 static blitter
*qt_blitter_new(void *handle
, int w
, int h
)
383 blitter
*bl
= snew(blitter
);
385 * We can't create the pixmap right now, because fe->window
386 * might not yet exist. So we just cache w and h and create it
387 * during the firs call to blitter_save.
395 static void qt_blitter_free(void *handle
, blitter
*bl
)
402 static void qt_blitter_save(void *handle
, blitter
*bl
, int x
, int y
)
404 frontend
*fe
= (frontend
*)handle
;
407 bl
->pixmap
= new QPixmap
;
411 *bl
->pixmap
= fe
->pixmap
->copy(bl
->x
, bl
->y
, bl
->w
, bl
->h
);
414 static void qt_blitter_load(void *handle
, blitter
*bl
, int x
, int y
)
416 frontend
*fe
= (frontend
*)handle
;
417 if (x
== BLITTER_FROMSAVED
&& y
== BLITTER_FROMSAVED
) {
422 fe
->painter
->drawPixmap(QPoint(x
, y
), *bl
->pixmap
);
425 static void qt_draw_update(void *handle
, int x
, int y
, int w
, int h
)
427 frontend
*fe
= (frontend
*)handle
;
428 if (fe
->bbox_l
> x
) fe
->bbox_l
= x
;
429 if (fe
->bbox_r
< x
+w
) fe
->bbox_r
= x
+w
;
430 if (fe
->bbox_u
> y
) fe
->bbox_u
= y
;
431 if (fe
->bbox_d
< y
+h
) fe
->bbox_d
= y
+h
;
434 const struct drawing_api qt_drawing
= {
445 NULL
, //qt_status_bar,
450 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, /* {begin,end}_{doc,page,puzzle} */
451 NULL
, NULL
, /* line_width, line_dotted */
452 NULL
, //text_fallback
453 NULL
, //draw_thick_line,
456 static void maybe_enlarge_window(frontend
*fe
)
458 QSize delta
= fe
->area
->sizeHint() - fe
->area
->size();
459 if (delta
.width() < 0) delta
.rwidth() = 0;
460 if (delta
.height() < 0) delta
.rheight() = 0;
461 if (delta
== QSize(0, 0))
462 fe
->area
->do_resize(fe
->window
->size());
464 fe
->window
->resize(fe
->window
->size() + delta
);
467 static void add_type_submenu(QMenu
*menu
, QWidget
*window
, frontend
*fe
, int npreset
)
469 QActionGroup
*itemgrp
= new QActionGroup(window
);
470 QSignalMapper
*signalMapper
= new QSignalMapper(window
);
471 window
->connect(signalMapper
, SIGNAL(mapped(QObject
*)),
472 window
, SLOT(preset(QObject
*)));
474 for (int i
= 0; i
< npreset
; i
++) {
478 midend_fetch_preset(fe
->me
, i
, &name
, ¶ms
);
480 QAction
*a
= new QAction(name
, window
);
482 window
->connect(a
, SIGNAL(triggered()), signalMapper
, SLOT(map()));
483 GameParams
*p
= new GameParams(params
);
484 signalMapper
->setMapping(a
, p
);
485 itemgrp
->addAction(menu
->menuAction());
489 PuzzleWindow::PuzzleWindow(QWidget
*parent
, Qt::WFlags fl
)
490 : QMainWindow( parent
, fl
)
495 fprintf(stderr
, "failed to allocate frontend\n");
499 fe
->timer
= new Timer(fe
);
501 fe
->me
= midend_new(fe
, &thegame
, &qt_drawing
, fe
);
502 midend_new_game(fe
->me
);
504 setWindowTitle(thegame
.name
);
505 QWidget
*vbox
= new QWidget(this);
506 QVBoxLayout
*vlayout
= new QVBoxLayout(vbox
);
507 vlayout
->setMargin(0);
510 fe
->painter
= new QPainter
;
513 QMenu
*menu
= QSoftMenuBar::menuFor(this);
515 QMenu
*menu
= this->menuBar()->addMenu("&Game");
517 menu
->addAction("New game", this, SLOT(newGame()));
519 if ((npreset
= midend_num_presets(fe
->me
)) > 0 || thegame
.can_configure
) {
521 QMenu
*submenu
= menu
->addMenu("Type");
523 QMenu
*submenu
= this->menuBar()->addMenu("Type");
525 add_type_submenu(submenu
, this, fe
, npreset
);
527 menu
->addAction("Restart", this, SLOT(restart()));
528 menu
->addAction("Undo", this, SLOT(undo()));
529 menu
->addAction("Redo", this, SLOT(redo()));
530 menu
->addAction("Solve", this, SLOT(solve()));
532 menu
->addSeparator();
533 QMenu
*submenu
= menu
;
535 menu
->addAction("Exit", qApp
, SLOT(quit()));
536 QMenu
*submenu
= this->menuBar()->addMenu("Help");
538 submenu
->addAction("About", this, SLOT(about()));
543 if (thegame
.flags
& REQUIRE_NUMPAD
) {
544 QSignalMapper
*signalMapper
= new QSignalMapper(this);
545 this->connect(signalMapper
, SIGNAL(mapped(int)),
546 this, SLOT(numpad(int)));
548 QWidget
*hbox
= new QWidget(this);
549 QHBoxLayout
*hlayout
= new QHBoxLayout(hbox
);
550 hlayout
->setMargin(0);
551 vlayout
->addWidget(hbox
);
555 for(buf
[0]='0'; buf
[0]<='9'; buf
[0]++) {
556 QPushButton
*button
= new QPushButton(buf
);
557 button
->setMinimumSize(1, 0); // FIXME we could do better
558 hlayout
->addWidget(button
);
559 this->connect(button
, SIGNAL(clicked()), signalMapper
, SLOT(map()));
560 signalMapper
->setMapping(button
, buf
[0]);
564 fe
->area
= new Canvas(fe
);
565 vlayout
->addWidget(fe
->area
);
566 setCentralWidget(vbox
);
570 QSize
Canvas::sizeHint() const {
574 * Currently I don't want to scale large
575 * puzzles to fit on the screen. This is because X does permit
576 * extremely large windows and many window managers provide a
577 * means of navigating round them, and the users I consulted
578 * before deciding said that they'd rather have enormous puzzle
579 * windows spanning multiple screen pages than have them
580 * shrunk. I could change my mind later or introduce
581 * configurability; this would be the place to do so, by
582 * replacing the initial values of x and y with the screen
587 midend_size(fe
->me
, &x
, &y
, FALSE
);
590 void PuzzleWindow::newGame() {
591 midend_process_key(fe
->me
, 0, 0, 'n');
593 void PuzzleWindow::preset(QObject
*o
) {
594 GameParams
*p
= qobject_cast
<GameParams
*>(o
);
597 midend_set_params(fe
->me
, p
->params
);
598 midend_new_game(fe
->me
);
599 //changed_preset(fe);
601 fe
->area
->do_resize(fe
->window
->size());
603 maybe_enlarge_window(fe
);
606 void PuzzleWindow::restart() {
607 midend_restart_game(fe
->me
);
609 void PuzzleWindow::undo() {
610 midend_process_key(fe
->me
, 0, 0, 'u');
612 void PuzzleWindow::redo() {
613 midend_process_key(fe
->me
, 0, 0, 'r');
615 void PuzzleWindow::solve() {
618 msg
= midend_solve(fe
->me
);
626 void PuzzleWindow::numpad(int n
) {
627 midend_process_key(fe
->me
, 0, 0, (char)n
);
629 void PuzzleWindow::about() {
633 sprintf(titlebuf
, "About %.200s", thegame
.name
);
636 "from Simon Tatham's Portable Puzzle Collection\n\n"
637 "%.500s", thegame
.name
, ver
);
640 mb
.setWindowTitle(titlebuf
);
647 QTOPIA_ADD_APPLICATION(QTOPIA_TARGET
,PuzzleWindow
)
650 int main(int argc
, char **argv
)
652 char *pname
= argv
[0];
653 int doing_opts
= TRUE
;
659 * Command line parsing in this function is rather fiddly,
660 * because GTK wants to have a go at argc/argv _first_ - and
661 * yet we can't let it, because gtk_init() will bomb out if it
662 * can't open an X display, whereas in fact we want to permit
663 * our --generate and --print modes to run without an X
667 * - we parse the command line ourselves, without modifying
669 * - if we encounter an error which might plausibly be the
670 * result of a GTK command line (i.e. not detailed errors in
671 * particular options of ours) we store the error message
672 * and terminate parsing.
673 * - if we got enough out of the command line to know it
674 * specifies a non-X mode of operation, we either display
675 * the stored error and return failure, or if there is no
676 * stored error we do the non-X operation and return
678 * - otherwise, we go straight to gtk_init().
684 if (doing_opts
&& !strcmp(p
, "--version")) {
685 printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
688 } else if (doing_opts
&& !strcmp(p
, "--")) {
691 sprintf(errbuf
, "%.100s: unrecognised option '%.100s'\n",
698 fputs(errbuf
, stderr
);
702 QApplication
app(argc
, argv
);