OSDN Git Service

2020.05.25 update
[rebornos/cnchi-gnome-osdn.git] / Cnchi / main_window.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # main_window.py
5 #
6 # Copyright © 2013-2018 RebornOS
7 #
8 # This file is part of Cnchi.
9 #
10 # Cnchi is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Cnchi is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # The following additional terms are in effect as per Section 7 of the license:
21 #
22 # The preservation of all legal notices and author attributions in
23 # the material or in the Appropriate Legal Notices displayed
24 # by works containing it is required.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
28
29
30 """ Main Cnchi Window """
31
32 import os
33 import multiprocessing
34 import logging
35
36 import config
37 import desktop_info
38 import info
39 import misc.extra as misc
40
41 import pages.welcome
42 import pages.language
43 import pages.location
44 import pages.cache
45 import pages.check
46 import pages.desktop
47 import pages.features
48 import pages.keymap
49 import pages.timezone
50 import pages.user_info
51 import pages.slides
52 import pages.summary
53 import pages.mirrors
54 import pages.ask
55 import pages.automatic
56 import pages.alongside
57 import pages.advanced
58 import pages.zfs
59
60 import gi
61 gi.require_version('Gtk', '3.0')
62 from gi.repository import Gtk, Gdk
63
64
65 # When testing, no _() is available
66 try:
67     _("")
68 except NameError as err:
69     def _(message):
70         return message
71
72 def atk_set_image_description(widget, description):
73     """ Sets the textual description for a widget that displays image/pixmap
74         information onscreen. """
75     atk_widget = widget.get_accessible()
76     if atk_widget is not None:
77         atk_widget.set_object_description(description)
78
79
80 def atk_set_object_description(widget, description):
81     """ Sets the textual description for a widget """
82     atk_widget = widget.get_accessible()
83     if atk_widget is not None:
84         atk_widget.set_image_description(description)
85         # atk_object_set_name
86
87
88 class MainWindow(Gtk.ApplicationWindow):
89     """ Cnchi main window """
90
91     def __init__(self, app, cmd_line):
92         Gtk.ApplicationWindow.__init__(self, title="Cnchi", application=app)
93
94         self._main_window_width = 875
95         self._main_window_height = 550
96
97         logging.info("Cnchi installer version %s", info.CNCHI_VERSION)
98
99         self.settings = config.Settings()
100         self.gui_dir = self.settings.get('ui')
101
102         if not os.path.exists(self.gui_dir):
103             cnchi_dir = os.path.join(os.path.dirname(__file__), './')
104             self.settings.set('cnchi', cnchi_dir)
105
106             gui_dir = os.path.join(os.path.dirname(__file__), 'ui/')
107             self.settings.set('ui', gui_dir)
108
109             data_dir = os.path.join(os.path.dirname(__file__), 'data/')
110             self.settings.set('data', data_dir)
111
112             self.gui_dir = self.settings.get('ui')
113
114         # By default, always try to use local /var/cache/pacman/pkg
115         xz_cache = ["/var/cache/pacman/pkg"]
116
117         # Check command line
118         if cmd_line.cache and cmd_line.cache not in xz_cache:
119             xz_cache.append(cmd_line.cache)
120
121         # Log cache dirs
122         for xz_path in xz_cache:
123             logging.debug(
124                 "Cnchi will use '%s' as a source for cached xz packages",
125                 xz_path)
126
127         # Store cache dirs in config
128         self.settings.set('xz_cache', xz_cache)
129
130         data_dir = self.settings.get('data')
131
132         # For things we are not ready for users to test
133         self.settings.set('hidden', cmd_line.hidden)
134
135         # a11y
136         self.settings.set('a11y', cmd_line.a11y)
137
138         # Set enabled desktops
139         if self.settings.get('hidden'):
140             self.settings.set('desktops', desktop_info.DESKTOPS_DEV)
141         elif self.settings.get('a11y'):
142             self.settings.set('desktops', desktop_info.DESKTOPS_A11Y)
143         else:
144             self.settings.set('desktops', desktop_info.DESKTOPS)
145
146         if cmd_line.environment:
147             my_desktop = cmd_line.environment.lower()
148             if my_desktop in desktop_info.DESKTOPS:
149                 self.settings.set('desktop', my_desktop)
150                 self.settings.set('desktop_ask', False)
151                 logging.debug(
152                     "Cnchi will install the %s desktop environment",
153                     my_desktop)
154
155         self.cnchi_ui = Gtk.Builder()
156         path = os.path.join(self.gui_dir, "cnchi.ui")
157         self.cnchi_ui.add_from_file(path)
158
159         main = self.cnchi_ui.get_object("main")
160         # main.set_property("halign", Gtk.Align.CENTER)
161         self.add(main)
162
163         self.header_ui = Gtk.Builder()
164         path = os.path.join(self.gui_dir, "header.ui")
165         self.header_ui.add_from_file(path)
166         self.header = self.header_ui.get_object("header")
167
168         self.logo = self.header_ui.get_object("logo")
169         path = os.path.join(
170             data_dir, "images", "antergos", "rebornos-logo-mini2.png")
171         self.logo.set_from_file(path)
172
173         # To honor our css
174         self.header.set_name("header")
175         self.logo.set_name("logo")
176
177         self.main_box = self.cnchi_ui.get_object("main_box")
178         # self.main_box.set_property("halign", Gtk.Align.CENTER)
179
180         ##self.main_box.set_property('width_request', 800)
181
182         self.progressbar = self.cnchi_ui.get_object("main_progressbar")
183         self.progressbar.set_name('process_progressbar')
184         # a11y
185         self.progressbar.set_can_focus(False)
186
187         self.forward_button = self.header_ui.get_object("forward_button")
188         self.backwards_button = self.header_ui.get_object("backwards_button")
189
190         # atk_set_image_description(self.forward_button, _("Next step"))
191         # atk_set_image_description(self.backwards_button, _("Previous step"))
192         # atk_set_object_description(self.forward_button, _("Next step"))
193         # atk_set_object_description(self.backwards_button, _("Previous step"))
194
195         self.forward_button.set_name('fwd_btn')
196         self.forward_button.set_always_show_image(True)
197
198         self.backwards_button.set_name('bk_btn')
199         self.backwards_button.set_always_show_image(True)
200
201         # a11y
202         if cmd_line.a11y:
203             self.forward_button.set_label(_("Next"))
204             self.backwards_button.set_label(_("Back"))
205
206         # Create a queue. Will be used to report pacman messages
207         # (pacman/pac.py) to the main thread (installation/process.py)
208         self.callback_queue = multiprocessing.JoinableQueue()
209
210         if cmd_line.packagelist:
211             self.settings.set('alternate_package_list', cmd_line.packagelist)
212             logging.info(
213                 "Using '%s' file as package list",
214                 self.settings.get('alternate_package_list'))
215
216         self.set_titlebar(self.header)
217
218         # Prepare params dict to pass common parameters to all screens
219         self.params = dict()
220         self.params['main_window'] = self
221         self.params['header'] = self.header
222         self.params['gui_dir'] = self.gui_dir
223         self.params['forward_button'] = self.forward_button
224         self.params['backwards_button'] = self.backwards_button
225         self.params['callback_queue'] = self.callback_queue
226         self.params['settings'] = self.settings
227         self.params['main_progressbar'] = self.progressbar
228
229         self.params['checks_are_optional'] = cmd_line.no_check
230         self.params['no_tryit'] = cmd_line.no_tryit
231         self.params['a11y'] = cmd_line.a11y
232
233         # Just load the first two screens (the other ones will be loaded later)
234         # We do this so the user has not to wait for all the screens to be
235         # loaded
236         self.pages = dict()
237         self.pages["welcome"] = pages.welcome.Welcome(self.params)
238
239         if os.path.exists('/home/antergos/.config/openbox'):
240             # In minimal iso, load language screen now
241             self.pages["language"] = pages.language.Language(self.params)
242
243             # Fix bugy Gtk window size when using Openbox
244             self._main_window_width = 750
245             self._main_window_height = 450
246
247         self.connect('delete-event', self.on_exit_button_clicked)
248         self.connect('key-release-event', self.on_key_release)
249
250         self.cnchi_ui.connect_signals(self)
251         self.header_ui.connect_signals(self)
252
253         nil, major, minor = info.CNCHI_VERSION.split('.')
254         name = 'Cnchi '
255         title_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
256         self.set_title(title_string)
257         self.header.set_title(title_string)
258         self.header.set_subtitle(_("RebornOS Installer"))
259         self.header.set_show_close_button(True)
260         self.tooltip_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
261         self.header.forall(self.header_for_all_callback, self.tooltip_string)
262
263         self.set_geometry()
264
265         # Set window icon
266         icon_path = os.path.join(
267             data_dir,
268             "images",
269             "antergos",
270             "rebornos-icon.png")
271         self.set_icon_from_file(icon_path)
272
273         # Set the first page to show
274
275         # If minimal iso is detected, skip the welcome page.
276         if os.path.exists('/home/antergos/.config/openbox'):
277             self.current_page = self.pages["language"]
278             self.settings.set('timezone_start', True)
279         else:
280             self.current_page = self.pages["welcome"]
281
282         self.main_box.add(self.current_page)
283
284         # Use our css file
285         style_provider = Gtk.CssProvider()
286
287         style_css = os.path.join(data_dir, "css", "gtk-style.css")
288
289         with open(style_css, 'rb') as css:
290             css_data = css.read()
291
292         style_provider.load_from_data(css_data)
293
294         Gtk.StyleContext.add_provider_for_screen(
295             Gdk.Screen.get_default(), style_provider,
296             Gtk.STYLE_PROVIDER_PRIORITY_USER
297         )
298
299         # Show main window
300         self.show_all()
301
302         self.current_page.prepare('forwards')
303
304         # Hide backwards button
305         self.backwards_button.hide()
306
307         self.progressbar.set_fraction(0)
308         self.progressbar_step = 0
309
310         # Do not hide progress bar for minimal iso as it would break
311         # the widget alignment on language page.
312         if not os.path.exists('/home/antergos/.config/openbox'):
313             # Hide progress bar
314             self.progressbar.hide()
315
316         self.set_focus(None)
317
318         misc.gtk_refresh()
319
320     def header_for_all_callback(self, widget, _data):
321         """ Show tooltip in header """
322         if isinstance(widget, Gtk.Box):
323             widget.forall(self.header_for_all_callback, self.tooltip_string)
324         elif widget.get_style_context().has_class('title'):
325             widget.set_tooltip_text(self.tooltip_string)
326
327     def load_pages(self):
328         """ Preload all installer pages """
329         if not os.path.exists('/home/antergos/.config/openbox'):
330             self.pages["language"] = pages.language.Language(self.params)
331
332         self.pages["check"] = pages.check.Check(self.params)
333         self.pages["location"] = pages.location.Location(self.params)
334
335         self.pages["timezone"] = pages.timezone.Timezone(self.params)
336
337         if self.settings.get('desktop_ask'):
338             self.pages["keymap"] = pages.keymap.Keymap(self.params)
339             self.pages["desktop"] = pages.desktop.DesktopAsk(self.params)
340             self.pages["features"] = pages.features.Features(self.params)
341         else:
342             self.pages["keymap"] = pages.keymap.Keymap(
343                 self.params,
344                 next_page='features')
345             self.pages["features"] = pages.features.Features(
346                 self.params,
347                 prev_page='keymap')
348
349         self.pages["cache"] = pages.cache.Cache(self.params)
350         self.pages["mirrors"] = pages.mirrors.Mirrors(self.params)
351
352         self.pages["installation_ask"] = pages.ask.InstallationAsk(
353             self.params)
354         self.pages["installation_automatic"] = pages.automatic.InstallationAutomatic(
355             self.params)
356
357         if self.settings.get("enable_alongside"):
358             self.pages["installation_alongside"] = pages.alongside.InstallationAlongside(
359                 self.params)
360         else:
361             self.pages["installation_alongside"] = None
362
363         self.pages["installation_advanced"] = pages.advanced.InstallationAdvanced(
364             self.params)
365         self.pages["installation_zfs"] = pages.zfs.InstallationZFS(
366             self.params)
367         self.pages["user_info"] = pages.user_info.UserInfo(self.params)
368         self.pages["summary"] = pages.summary.Summary(self.params)
369         self.pages["slides"] = pages.slides.Slides(self.params)
370
371         diff = 2
372         if os.path.exists('/home/antergos/.config/openbox'):
373             # In minimal (openbox) we don't have a welcome screen
374             diff = 3
375
376         num_pages = len(self.pages) - diff
377
378         if num_pages > 0:
379             self.progressbar_step = 1.0 / num_pages
380
381     def set_geometry(self):
382         """ Sets Cnchi window geometry """
383         self.set_position(Gtk.WindowPosition.CENTER)
384         self.set_resizable(False)
385
386         (min_width, natural_width) = self.get_preferred_width()
387         (min_height, natural_height) = self.get_preferred_width()
388         logging.debug("Main window minimal size: %dx%d", min_width, min_height)
389         logging.debug("Main window natural size: %dx%d", natural_width, natural_height)
390         logging.debug("Setting main window size to %dx%d",
391                       self._main_window_width, self._main_window_height)
392
393         self.set_size_request(self._main_window_width,
394                               self._main_window_height)
395         self.set_default_size(self._main_window_width,
396                               self._main_window_height)
397
398         geom = Gdk.Geometry()
399         geom.min_width = self._main_window_width
400         geom.min_height = self._main_window_height
401         geom.max_width = self._main_window_width
402         geom.max_height = self._main_window_height
403         geom.base_width = self._main_window_width
404         geom.base_height = self._main_window_height
405         geom.width_inc = 0
406         geom.height_inc = 0
407
408         hints = (Gdk.WindowHints.MIN_SIZE |
409                  Gdk.WindowHints.MAX_SIZE |
410                  Gdk.WindowHints.BASE_SIZE |
411                  Gdk.WindowHints.RESIZE_INC)
412
413         self.set_geometry_hints(None, geom, hints)
414
415     def on_key_release(self, _widget, event, _data=None):
416         """ Callback called when a key is released """
417         if event.keyval == Gdk.keyval_from_name('Escape'):
418             response = self.confirm_quitting()
419             if response == Gtk.ResponseType.YES:
420                 self.on_exit_button_clicked(self)
421                 self.destroy()
422
423     def confirm_quitting(self):
424         """ Shows confirmation message before quitting """
425         message = Gtk.MessageDialog(
426             transient_for=self,
427             modal=True,
428             destroy_with_parent=True,
429             message_type=Gtk.MessageType.QUESTION,
430             buttons=Gtk.ButtonsType.YES_NO,
431             text=_("Do you really want to quit the installer?"))
432         response = message.run()
433         message.destroy()
434         return response
435
436     def on_exit_button_clicked(self, _widget, _data=None):
437         """ Quit Cnchi """
438         try:
439             misc.remove_temp_files(self.settings.get('temp'))
440             logging.info("Quiting installer...")
441             for proc in multiprocessing.active_children():
442                 proc.terminate()
443             logging.shutdown()
444         except KeyboardInterrupt:
445             pass
446
447     def set_progressbar_step(self, add_value):
448         """ Update progress bar """
449         new_value = self.progressbar.get_fraction() + add_value
450         if new_value > 1:
451             new_value = 1
452         if new_value < 0:
453             new_value = 0
454         self.progressbar.set_fraction(new_value)
455         if new_value > 0:
456             self.progressbar.show()
457         else:
458             self.progressbar.hide()
459
460     def on_forward_button_clicked(self, _widget, _data=None):
461         """ Show next screen """
462         next_page = self.current_page.get_next_page()
463
464         if next_page is not None:
465             # self.logo.hide()
466             if next_page not in self.pages.keys():
467                 # Load all pages
468                 self.load_pages()
469                 self.progressbar_step = 1.0 / (len(self.pages) - 2)
470
471             stored = self.current_page.store_values()
472
473             if stored:
474                 self.set_progressbar_step(self.progressbar_step)
475                 self.main_box.remove(self.current_page)
476
477                 self.current_page = self.pages[next_page]
478
479                 if self.current_page is not None:
480                     self.current_page.prepare('forwards')
481                     self.main_box.add(self.current_page)
482                     if self.current_page.get_prev_page() is not None:
483                         # There is a previous page, show back button
484                         self.backwards_button.show()
485                         self.backwards_button.set_sensitive(True)
486                     else:
487                         # We can't go back, hide back button
488                         self.backwards_button.hide()
489                         if self.current_page == "slides":
490                             # Show logo in slides screen
491                             self.logo.show_all()
492
493     def on_backwards_button_clicked(self, _widget, _data=None):
494         """ Show previous screen """
495         prev_page = self.current_page.get_prev_page()
496
497         if prev_page is not None:
498             self.set_progressbar_step(-self.progressbar_step)
499
500             # If we go backwards, don't store user changes
501             # self.current_page.store_values()
502
503             self.main_box.remove(self.current_page)
504
505             self.current_page = self.pages[prev_page]
506
507             if self.current_page is not None:
508                 self.current_page.prepare('backwards')
509                 self.main_box.add(self.current_page)
510
511                 if self.current_page.get_prev_page() is None:
512                     # We're at the first page
513                     self.backwards_button.hide()
514                     self.progressbar.hide()
515                     self.logo.show_all()