示例程序:使用开罗创建时钟

既然我们已经将使用开罗画图的基础知识介绍完了,那么现在让我们尝试将所有的内容放在一起,创建一个可以执行某些操作的简单应用程序。以下示例使用开罗创建一个自定义的Clock部件。这个时钟有秒针、分针、时针,并且每一秒更新一次。

源代码

File: clock.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_CLOCK_H
#define GTKMM_EXAMPLE_CLOCK_H

#include <gtkmm/drawingarea.h>

class Clock : public Gtk::DrawingArea
{
public:
  Clock();
  virtual ~Clock();

protected:
  void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

  bool on_timeout();

  double m_radius;
  double m_line_width;
};

#endif // GTKMM_EXAMPLE_CLOCK_H

File: main.cc (For use with gtkmm 4)

#include "clock.h"
#include <gtkmm/application.h>
#include <gtkmm/window.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();

protected:
  Clock m_clock;
};

ExampleWindow::ExampleWindow()
{
  set_title("Cairomm Clock");
  set_child(m_clock);
}

int main(int argc, char** argv)
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: clock.cc (For use with gtkmm 4)

#include <ctime>
#include <cmath>
#include <cairomm/context.h>
#include <glibmm/main.h>
#include "clock.h"

Clock::Clock()
: m_radius(0.42), m_line_width(0.05)
{
  Glib::signal_timeout().connect( sigc::mem_fun(*this, &Clock::on_timeout), 1000 );
  set_draw_func(sigc::mem_fun(*this, &Clock::on_draw));
}

Clock::~Clock()
{
}

void Clock::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
  // scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e.
  // the center of the window
  cr->scale(width, height);
  cr->translate(0.5, 0.5);
  cr->set_line_width(m_line_width);

  cr->save();
  cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
  cr->paint();
  cr->restore();
  cr->arc(0, 0, m_radius, 0, 2 * M_PI);
  cr->save();
  cr->set_source_rgba(1.0, 1.0, 1.0, 0.8);
  cr->fill_preserve();
  cr->restore();
  cr->stroke_preserve();
  cr->clip();

  //clock ticks
  for (int i = 0; i < 12; i++)
  {
    double inset = 0.05;

    cr->save();
    cr->set_line_cap(Cairo::Context::LineCap::ROUND);

    if(i % 3 != 0)
    {
      inset *= 0.8;
      cr->set_line_width(0.03);
    }

    cr->move_to(
      (m_radius - inset) * cos (i * M_PI / 6),
      (m_radius - inset) * sin (i * M_PI / 6));
    cr->line_to (
      m_radius * cos (i * M_PI / 6),
      m_radius * sin (i * M_PI / 6));
    cr->stroke();
    cr->restore(); /* stack-pen-size */
  }

  // store the current time
  time_t rawtime;
  time(&rawtime);
  struct tm * timeinfo = localtime (&rawtime);

  // compute the angles of the indicators of our clock
  double minutes = timeinfo->tm_min * M_PI / 30;
  double hours = timeinfo->tm_hour * M_PI / 6;
  double seconds= timeinfo->tm_sec * M_PI / 30;

  cr->save();
  cr->set_line_cap(Cairo::Context::LineCap::ROUND);

  // draw the seconds hand
  cr->save();
  cr->set_line_width(m_line_width / 3);
  cr->set_source_rgba(0.7, 0.7, 0.7, 0.8); // gray
  cr->move_to(0, 0);
  cr->line_to(sin(seconds) * (m_radius * 0.9),
    -cos(seconds) * (m_radius * 0.9));
  cr->stroke();
  cr->restore();

  // draw the minutes hand
  cr->set_source_rgba(0.117, 0.337, 0.612, 0.9);   // blue
  cr->move_to(0, 0);
  cr->line_to(sin(minutes + seconds / 60) * (m_radius * 0.8),
    -cos(minutes + seconds / 60) * (m_radius * 0.8));
  cr->stroke();

  // draw the hours hand
  cr->set_source_rgba(0.337, 0.612, 0.117, 0.9);   // green
  cr->move_to(0, 0);
  cr->line_to(sin(hours + minutes / 12.0) * (m_radius * 0.5),
    -cos(hours + minutes / 12.0) * (m_radius * 0.5));
  cr->stroke();
  cr->restore();

  // draw a little dot in the middle
  cr->arc(0, 0, m_line_width / 3.0, 0, 2 * M_PI);
  cr->fill();
}

bool Clock::on_timeout()
{
  // force our program to redraw the entire clock.
  queue_draw();
  return true;
}

和之前一样,所有有趣的东西都在绘图函数on_draw()中完成。在深入研究绘图函数之前,请注意在Clock部件的构造函数中将处理函数on_timeout()连接到了用计时器上,该计时器的超时时间为1000毫秒(1秒)。这意味着on_timeout()将每秒被调用一次。这个函数的唯一任务就是使窗口无效,以便gtkmm强制重绘它。

现在让我们研究实际执行绘图的函数。到现在on_draw()函数的第一部分你应该很熟悉了。此示例再次将坐标系缩放为单位正方形,以便更轻松的按窗口大小的百分比绘制时钟。此外坐标系是按比例缩放的,所以(0,0)坐标位于窗口的正中央。

Cairo::Context::paint()方法用于设置窗口的背景色。这个函数不接受任何参数,并使用处于活跃状态的源颜色填充当前表面(或表面裁剪的一部分)。设置背景色以后,我们画一个圆作为时钟的轮廓,用白色填充它然后用黑色进行描边。请注意,这两个操作都用_preserve变体以保留当前路径,然后对相同的路径进行剪辑以确保我们下一行不会超过时钟的轮廓。

绘制轮廓后,我们围绕时钟为每个小时绘制可读线并为12、3、6、9绘制相对更大的刻度线。接着我们就可以实现时钟的计时功能了 -- 获取当前时间的时、分、秒然后以正确的角度将指针绘制出来。