~stefan-schwarzburg/qreator/qreator

1 by David Planella
Initial project creation with Quickly!
1
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
### BEGIN LICENSE
20 by David Planella
Added license
3
# Copyright (C) 2012 David Planella <david.planella@ubuntu.com>
72 by David Planella
Updated version to follow that of the package
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU General Public License version 3, as published
20 by David Planella
Added license
6
# by the Free Software Foundation.
72 by David Planella
Updated version to follow that of the package
7
#
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranties of
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
20 by David Planella
Added license
11
# PURPOSE.  See the GNU General Public License for more details.
72 by David Planella
Updated version to follow that of the package
12
#
13
# You should have received a copy of the GNU General Public License along
20 by David Planella
Added license
14
# with this program.  If not, see <http://www.gnu.org/licenses/>.
1 by David Planella
Initial project creation with Quickly!
15
### END LICENSE
16
21 by David Planella
Started adding more bling to the QR code drawing area with Cairo
17
import cairo
18
import math
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
19
from gi.repository import Gtk, Gdk, GdkPixbuf  # pylint: disable=E0611
1 by David Planella
Initial project creation with Quickly!
20
import logging
21
logger = logging.getLogger('qreator')
22
61.1.16 by David Planella
Simplified internationalization code and made it work with /opt without having to depend on quickly packaging changes
23
from qreator_lib.i18n import _
1 by David Planella
Initial project creation with Quickly!
24
from qreator_lib import Window
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
25
from qreator_lib.helpers import get_media_file
26
27
from QRCode import QRCode
28
from QRCode import QRCodeOutput
3 by David Planella
First working version
29
17 by David Planella
First working version ready for release
30
COL_DESC = 0
3 by David Planella
First working version
31
COL_PIXBUF = 1
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
32
COL_ID = 2
3 by David Planella
First working version
33
17 by David Planella
First working version ready for release
34
PAGE_NEW = 0
35
#PAGE_HISTORY = 1
36
PAGE_ABOUT = 1
37
PAGE_QR = 2
3 by David Planella
First working version
38
39
1 by David Planella
Initial project creation with Quickly!
40
# See qreator_lib.Window.py for more details about how this class works
41
class QreatorWindow(Window):
42
    __gtype_name__ = "QreatorWindow"
3 by David Planella
First working version
43
17 by David Planella
First working version ready for release
44
    def finish_initializing(self, builder):  # pylint: disable=E1002
1 by David Planella
Initial project creation with Quickly!
45
        """Set up the main window"""
46
        super(QreatorWindow, self).finish_initializing(builder)
47
48
        # Code for other initialization actions should be added here.
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
49
50
        # Initialize the clipboard
3 by David Planella
First working version
51
        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
52
53
        # Initialize the style for the main toolbar
12 by David Planella
Moved the QR generation code to a separate module, independent of the UI
54
        context = self.ui.toolbar1.get_style_context()
55
        context.add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
56
57
        # Initialize the Cairo surface that will contain the QR code
3 by David Planella
First working version
58
        self.surface = None
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
59
60
        # Hide the notebook tabs
3 by David Planella
First working version
61
        self.ui.notebook1.set_show_tabs(False)
14 by David Planella
Added support to display maps with libchamplain. python-osmgpsmap, as an alternative without Clutter, does not seem to work because it's not introspectable -gtk3 branch is still WIP. Commented out GSettings code, at it seems to crash Winpdb (settings schema not found)
62
32 by David Planella
Added About dialog - thanks Damian for the help http://is.gd/9ouXsz
63
        # Initialize about dialog
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
64
        self.init_about_dialog()
65
66
        # Initialize the QR types icon view
67
        self.init_qr_types()
68
69
        # Load the background texture in the QR code page
61.1.14 by David Planella
Moved the USC database initialization code to be a task and a callback, in a first attempt to run it asynchronously. Started trying that with python-defer
70
        self.texture = cairo.ImageSurface.create_from_png(
71
                                                get_media_file("pattern.png"))
72
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
73
        # Add an initial text, so that there is an initial QR code
74
        self.qr_code_placeholder = 'http://launchpad.net/qreator'
75
76
    def init_qr_types(self):
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
77
        # Set up the QR types shown in the main icon view
78
        self.ui.qr_types_view.set_text_column(COL_DESC)
79
        self.ui.qr_types_view.set_pixbuf_column(COL_PIXBUF)
80
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
81
        from qrcodes.QRCodeText import QRCodeText
82
        from qrcodes.QRCodeURL import QRCodeURL
83
        from qrcodes.QRCodeLocation import QRCodeLocation
84
        from qrcodes.QRCodeWifi import QRCodeWifi
85
        from qrcodes.QRCodeSoftwareCenterApp import QRCodeSoftwareCenterApp
86
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
87
        self.qr_types = [
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
88
            QRCodeURL(self.update_qr_code, 'url.png', _('URL'), 0),
89
            QRCodeText(self.update_qr_code, 'text.png', _('Text'), 1),
90
            QRCodeLocation(self.update_qr_code, 'location.png',
91
                _('Geolocation'), 2),
92
            QRCodeWifi(self.update_qr_code, 'wifi.png', _('Wifi network'), 3),
93
            QRCodeSoftwareCenterApp(self.update_qr_code, 'softwarecentre.png',
94
                _('Ubuntu Software Center app'), 4),
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
95
        ]
96
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
97
        self.qr_types_store = Gtk.ListStore(str, GdkPixbuf.Pixbuf, int)
98
        # ^ desc, icon, id ^
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
99
        self.qr_types_store.set_sort_column_id(COL_DESC,
100
                                               Gtk.SortType.ASCENDING)
101
        self.ui.qr_types_view.set_model(self.qr_types_store)
102
        self.fill_qr_types_store()
103
104
        self.curr_height = 0
105
        self.curr_width = 0
106
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
107
        for qr_type in self.qr_types:
82 by Stefan Schwarzburg
replaced qr_input_notebook with qr_input_box, because boxes are much easier to automatically resize.
108
            self.ui.qr_input_box.add(qr_type.widget.grid)
79 by David Planella
An attempt to get notebook pages to resize. Thanks to xubuntix for contributing to the code
109
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
110
    def fill_qr_types_store(self):
111
        self.qr_types_store.clear()
112
113
        for qr_type in self.qr_types:
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
114
            icon = GdkPixbuf.Pixbuf.new_from_file(get_media_file(
115
                            qr_type.icon_path))
116
            self.qr_types_store.append([qr_type.description,
117
                                        icon,
118
                                        qr_type.id])
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
119
120
    def on_qreator_window_check_resize(self, widget):
121
        '''We need this function to fix the issue described at
122
        http://bit.ly/LW94BO whereby the number of columns of the icon view
123
        widget stays fixed at the number set for the initial width of the
124
        window'''
125
        # Get the new size
126
        new_width = widget.get_size()[0]
127
        new_height = widget.get_size()[1]
128
        # If the size has changed...
129
        if(new_width != self.curr_width or new_height != self.curr_height):
130
            # Remember new size
131
            self.curr_width = new_width
132
            self.curr_height = new_height
133
            # and refill iconviews with icons to adjust columns number
134
            self.fill_qr_types_store()
135
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
136
    def init_about_dialog(self):
137
        # Initialize about dialog
138
        about = Gtk.AboutDialog()
139
        about.set_program_name("Qreator")
140
        about.set_copyright(
141
            "Copyright (c) 2012 David Planella" +
142
            "\nhttp://about.me/david.planella")
143
        about.set_website("https://launchpad.net/qreator")
144
145
        about.set_version('12.05.6')
146
        about.set_authors([
147
            'David Planella <david.planella@ubuntu.com>',
148
            'Michael Hall <mhall119@ubuntu.com>',
149
            'Andrew Starr-Bochicchio <andrewsomething@ubuntu.com >',
150
            ])
151
        about.set_license(_('Distributed under the GPL v3 license.') +
152
                '\nhttp://www.opensource.org/licenses/gpl-3.0.html')
153
154
        about.set_translator_credits(_("translator-credits"))
155
156
        box = self.ui.about_box
157
        about.vbox.reparent(box)
158
159
        # Get rid of the 'Close' button
160
        for button in about.action_area.get_children():
161
            if button.get_property('label') == 'gtk-close':
162
                button.destroy()
163
164
##########################################
14 by David Planella
Added support to display maps with libchamplain. python-osmgpsmap, as an alternative without Clutter, does not seem to work because it's not introspectable -gtk3 branch is still WIP. Commented out GSettings code, at it seems to crash Winpdb (settings schema not found)
165
17 by David Planella
First working version ready for release
166
    def on_toolbuttonNew_clicked(self, widget, data=None):
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
167
        '''Shows the home page'''
17 by David Planella
First working version ready for release
168
        self.ui.notebook1.set_current_page(PAGE_NEW)
3 by David Planella
First working version
169
170
    def on_toolbuttonHistory_clicked(self, widget, data=None):
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
171
        '''Shows the history page'''
17 by David Planella
First working version ready for release
172
        pass  # self.ui.notebook1.set_current_page(PAGE_HISTORY)
3 by David Planella
First working version
173
174
    def on_toolbuttonAbout_clicked(self, widget, data=None):
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
175
        '''Shows the about page'''
3 by David Planella
First working version
176
        self.ui.notebook1.set_current_page(PAGE_ABOUT)
177
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
178
##########################################
17 by David Planella
First working version ready for release
179
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
180
    def on_qr_types_view_item_activated(self, widget, item):
16 by David Planella
Added some documentation, cleaned up the QRCode's object methods a bit
181
        '''Loads the UI for the appropriate QR type'''
14 by David Planella
Added support to display maps with libchamplain. python-osmgpsmap, as an alternative without Clutter, does not seem to work because it's not introspectable -gtk3 branch is still WIP. Commented out GSettings code, at it seems to crash Winpdb (settings schema not found)
182
183
        model = widget.get_model()
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
184
        qr_id = model[item][COL_ID]
185
        self.qr_types[qr_id].widget.on_activated()
17 by David Planella
First working version ready for release
186
187
        self.ui.notebook1.set_current_page(PAGE_QR)
82 by Stefan Schwarzburg
replaced qr_input_notebook with qr_input_box, because boxes are much easier to automatically resize.
188
189
        for child in self.ui.qr_input_box.get_children():
190
            child.hide()
191
        self.ui.qr_input_box.get_children()[qr_id].show()
192
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
193
194
##########################################
3 by David Planella
First working version
195
196
    def get_pixbuf_from_drawing_area(self):
17 by David Planella
First working version ready for release
197
        window = self.ui.qr_drawingarea.get_window()
5 by David Planella
Removed whitespace
198
17 by David Planella
First working version ready for release
199
        src_x, src_y = self.get_centered_coordinates(self.ui.qr_drawingarea,
3 by David Planella
First working version
200
                                                     self.surface)
11 by David Planella
Set up automatic resizing of images through PIL, and 3 standard output image sizes. Fixed RGBA > BGRA conversion going from PIL to Cairo
201
        image_height = self.surface.get_height()
202
        image_width = self.surface.get_width()
5 by David Planella
Removed whitespace
203
3 by David Planella
First working version
204
        return Gdk.pixbuf_get_from_window(window, src_x, src_y,
205
                                          image_width, image_height)
206
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
207
##########################################
208
3 by David Planella
First working version
209
    def on_toolbuttonSave_clicked(self, widget, data=None):
210
        if not self.surface:
211
            return
5 by David Planella
Removed whitespace
212
7 by David Planella
Added a file chooser to save the resulting image. Started playing with fade ins.
213
        dialog = Gtk.FileChooserDialog(_("Please choose a file"), self,
214
            Gtk.FileChooserAction.SAVE,
215
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
216
             Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
217
218
        filter_png = Gtk.FileFilter()
219
        filter_png.set_name(_("PNG images"))
220
        filter_png.add_mime_type("image/png")
221
        dialog.add_filter(filter_png)
222
223
        response = dialog.run()
224
225
        if response == Gtk.ResponseType.OK:
226
            # We cannot write directly from the surface, as the
227
            # Surface.write_to_png() method writes the image in the original
228
            # size returned by qrencode (i.e. non-scaled), and the
229
            # SurfacePattern does not have any methods to write to disk.
230
            # So we read the contents from the Gtk.DrawingArea, put them into
231
            # a Gdk.Pixbuf and use its 'savev' method to write to disk.
232
233
            pixbuf = self.get_pixbuf_from_drawing_area()
234
235
            pixbuf.savev(dialog.get_filename(), 'png', [], [])
236
237
        dialog.destroy()
3 by David Planella
First working version
238
239
    def on_toolbuttonCopy_clicked(self, widget, data=None):
240
        if not self.surface:
241
            return
5 by David Planella
Removed whitespace
242
3 by David Planella
First working version
243
        pixbuf = self.get_pixbuf_from_drawing_area()
244
        self.clipboard.set_image(pixbuf)
5 by David Planella
Removed whitespace
245
75 by David Planella
Decoupled QR code types from the QR window and from a particular GUI toolkit (right now only GTK is supported)
246
##########################################
247
17 by David Planella
First working version ready for release
248
    def update_qr_code(self, text):
249
        self.qr_code_placeholder = text
250
        self.ui.qr_drawingarea.queue_draw()
251
3 by David Planella
First working version
252
    def get_centered_coordinates(self, drawing_area, surface):
5 by David Planella
Removed whitespace
253
3 by David Planella
First working version
254
        drawing_area_height = drawing_area.get_allocated_height()
255
        drawing_area_width = drawing_area.get_allocated_width()
9 by David Planella
Scaling is now done at the source
256
        image_height = surface.get_height()
257
        image_width = surface.get_width()
3 by David Planella
First working version
258
5 by David Planella
Removed whitespace
259
        return (drawing_area_width / 2 - image_width / 2,
260
                drawing_area_height / 2 - image_height / 2)
3 by David Planella
First working version
261
17 by David Planella
First working version ready for release
262
    def on_qr_drawingarea_draw(self, widget, ctx, data=None):
14 by David Planella
Added support to display maps with libchamplain. python-osmgpsmap, as an alternative without Clutter, does not seem to work because it's not introspectable -gtk3 branch is still WIP. Commented out GSettings code, at it seems to crash Winpdb (settings schema not found)
263
        text = self.qr_code_placeholder
3 by David Planella
First working version
264
22 by David Planella
Some cleanup in the Cairo code
265
        ## Fill the background
266
267
        # Create a rounded rectanble
21 by David Planella
Started adding more bling to the QR code drawing area with Cairo
268
        x = 0.0
22 by David Planella
Some cleanup in the Cairo code
269
        y = 0.0
21 by David Planella
Started adding more bling to the QR code drawing area with Cairo
270
        width = widget.get_allocated_width()
271
        height = widget.get_allocated_height()
272
        aspect = 1.0
273
        corner_radius = height / 50.0
274
275
        radius = corner_radius / aspect
276
        degrees = math.pi / 180.0
277
22 by David Planella
Some cleanup in the Cairo code
278
        ctx.new_sub_path()
279
        ctx.arc(x + width - radius, y + radius, radius, -90 * degrees,
280
                0 * degrees)
281
        ctx.arc(x + width - radius, y + height - radius, radius, 0 * degrees,
282
                90 * degrees)
283
        ctx.arc(x + radius, y + height - radius, radius, 90 * degrees,
284
                180 * degrees)
285
        ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
286
        ctx.close_path()
287
288
        # Fill the rounded rectangle with a linear gradient
21 by David Planella
Started adding more bling to the QR code drawing area with Cairo
289
        lg = cairo.LinearGradient(0.0, 0.0, 500.0, 500.0)
61.1.14 by David Planella
Moved the USC database initialization code to be a task and a callback, in a first attempt to run it asynchronously. Started trying that with python-defer
290
        lg.add_color_stop_rgba(0, 0.27, 0.27, 0.27, 1)  # 1,1,1 is white
61.1.13 by David Planella
Added proper icon for the Software Centre app QR codes. Added texture to the background of the QR code image
291
        lg.add_color_stop_rgba(1, 0.22, 0.22, 0.22, 1)
22 by David Planella
Some cleanup in the Cairo code
292
21 by David Planella
Started adding more bling to the QR code drawing area with Cairo
293
        ctx.set_source(lg)
294
        ctx.fill()
61.1.14 by David Planella
Moved the USC database initialization code to be a task and a callback, in a first attempt to run it asynchronously. Started trying that with python-defer
295
61.1.15 by David Planella
Fixed a bug whereby the number of icon columns in the main iconview stayed always fixed at 1
296
        # Load a texture and overlay it to the gradient, tiled
61.1.13 by David Planella
Added proper icon for the Software Centre app QR codes. Added texture to the background of the QR code image
297
        pattern = cairo.SurfacePattern(self.texture)
298
        pattern.set_extend(cairo.EXTEND_REPEAT)
299
        ctx.set_source(pattern)
300
        ctx.paint()
61.1.14 by David Planella
Moved the USC database initialization code to be a task and a callback, in a first attempt to run it asynchronously. Started trying that with python-defer
301
74 by David Planella
Added icons for the 'clear' action to all text entries, used symbolic icons in the QR page, delayed loading Software Centre apps until the apps tab is loaded for the first time
302
        if not text == '':
303
            ## Create the QR code
304
            qr_code = QRCode()
305
            self.surface = qr_code.encode(text, QRCodeOutput.CAIRO_SURFACE)
306
307
            # Center the image in the drawing area
308
            centered_width, centered_height = \
309
                self.get_centered_coordinates(widget, self.surface)
310
            ctx.translate(centered_width, centered_height)
311
312
            # Set the surface as the context's source
313
            ctx.set_source_surface(self.surface)
314
315
            # Render the image
316
            ctx.paint()