예제 #1
0
    def _create_headerbar(self):
        # User/help menu
        user = api.get_current_user(self.store)
        xml = MENU_XML.format(username=stoq_api.escape(user.get_description()),
                              preferences=_('Preferences...'), password=_('Change password...'),
                              signout=_('Sign out...'), help=_('Help'),
                              contents=_('Contents'), translate=_('Translate Stoq...'),
                              get_support=_('Get support online...'), chat=_('Online chat...'),
                              about=_('About'), quit=_('Quit'))
        builder = Gtk.Builder.new_from_string(xml, -1)

        # Header bar
        self.header_bar = Gtk.HeaderBar()
        self.toplevel.set_titlebar(self.header_bar)

        # Right side
        self.close_btn = self.create_button('fa-power-off-symbolic', action='stoq.quit')
        self.close_btn.set_relief(Gtk.ReliefStyle.NONE)
        self.min_btn = self.create_button('fa-window-minimize-symbolic')
        self.min_btn.set_relief(Gtk.ReliefStyle.NONE)
        #self.header_bar.pack_end(self.close_btn)
        #self.header_bar.pack_end(self.min_btn)
        box = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        box.pack_start(self.min_btn, False, False, 0)
        box.pack_start(self.close_btn, False, False, 0)
        self.header_bar.pack_end(box)

        self.user_menu = builder.get_object('app-menu')
        self.help_section = builder.get_object('help-section')
        self.user_button = self.create_button('fa-cog-symbolic',
                                              menu_model=self.user_menu)
        self.search_menu = Gio.Menu()
        self.search_button = self.create_button('fa-search-symbolic', _('Searches'),
                                                menu_model=self.search_menu)
        self.main_menu = Gio.Menu()
        self.menu_button = self.create_button('fa-bars-symbolic', _('Actions'),
                                              menu_model=self.main_menu)

        self.header_bar.pack_end(
            ButtonGroup([self.menu_button, self.search_button, self.user_button]))

        self.sign_button = self.create_button('', _('Sign now'), style_class='suggested-action')
        #self.header_bar.pack_end(self.sign_button)

        # Left side
        self.home_button = self.create_button(STOQ_LAUNCHER, style_class='suggested-action')
        self.new_menu = Gio.Menu()
        self.new_button = self.create_button('fa-plus-symbolic', _('New'),
                                             menu_model=self.new_menu)

        self.header_bar.pack_start(
            ButtonGroup([self.home_button, self.new_button, ]))

        self.domain_header = None
        self.header_bar.show_all()

        self.notifications = NotificationCounter(self.home_button, blink=True)
예제 #2
0
파일: calendar.py 프로젝트: 5l1v3r1/stoq-1
    def create_ui(self):
        self.window.add_extra_items([
            self.ClientCallEvents, self.ClientBirthdaysEvents,
            self.AccountsPayableEvents, self.AccountsReceivableEvents,
            self.PurchaseEvents, self.WorkOrderEvents
        ],
                                    label=_('View'))
        self.window.add_new_items([
            self.NewClientCall, self.NewPayable, self.NewReceivable,
            self.NewWorkOrder
        ])

        # Reparent the toolbar, to show the date next to it.
        self.hbox = Gtk.HBox()

        self.date_label.set_xalign(0)

        self.main_vbox.pack_start(self.hbox, False, False, 6)
        self.main_vbox.pack_start(self._calendar, True, True, 0)

        self.nav_header = ButtonGroup([
            self.window.create_button('fa-arrow-left-symbolic',
                                      action='calendar.Back'),
            self.window.create_button('fa-calendar-symbolic',
                                      action='calendar.Today',
                                      tooltip=_('Today')),
            self.window.create_button('fa-arrow-right-symbolic',
                                      action='calendar.Forward')
        ])

        self.month_button = Gtk.ToggleButton.new_with_label(_('Month'))
        self.week_button = Gtk.ToggleButton.new_with_label(_('Week'))
        self.day_button = Gtk.ToggleButton.new_with_label(_('Day'))
        self.view_header = ButtonGroup([
            self.month_button,
            self.week_button,
            self.day_button,
        ])
        view = api.user_settings.get('calendar-view', 'month')
        if view == 'basicDay':
            self.day_button.set_active(True)
        elif view == 'basicWeek':
            self.week_button.set_active(True)
        else:
            self.month_button.set_active(True)

        self.hbox.pack_start(self.nav_header, False, False, 6)
        self.hbox.pack_start(self.date_label, True, True, 6)
        self.hbox.pack_start(self.view_header, False, False, 6)
        self.main_vbox.show_all()
예제 #3
0
    def add_domain_header(self, options):
        if self.domain_header:
            self.header_bar.remove(self.domain_header)
            self.domain_header = None

        if not options:
            return

        buttons = []
        for (icon, label, action, in_header) in options:
            if not in_header:
                continue
            buttons.append(
                self.create_button(icon, action=action, tooltip=label))
        self.domain_header = ButtonGroup(buttons)
        self.domain_header.show_all()
        self.header_bar.pack_start(self.domain_header)
예제 #4
0
    def create_ui(self):
        self.window.add_extra_items([
            self.ClientCallEvents,
            self.ClientBirthdaysEvents,
            self.AccountsPayableEvents,
            self.AccountsReceivableEvents,
            self.PurchaseEvents,
            self.WorkOrderEvents
        ], label=_('View'))
        self.window.add_new_items([self.NewClientCall,
                                   self.NewPayable,
                                   self.NewReceivable, self.NewWorkOrder])

        # Reparent the toolbar, to show the date next to it.
        self.hbox = Gtk.HBox()

        self.date_label.set_xalign(0)

        self.main_vbox.pack_start(self.hbox, False, False, 6)
        self.main_vbox.pack_start(self._calendar, True, True, 0)

        self.nav_header = ButtonGroup([
            self.window.create_button('fa-arrow-left-symbolic', action='calendar.Back'),
            self.window.create_button('fa-calendar-symbolic',
                                      action='calendar.Today',
                                      tooltip=_('Today')),
            self.window.create_button('fa-arrow-right-symbolic', action='calendar.Forward')
        ])

        self.month_button = Gtk.ToggleButton.new_with_label(_('Month'))
        self.week_button = Gtk.ToggleButton.new_with_label(_('Week'))
        self.day_button = Gtk.ToggleButton.new_with_label(_('Day'))
        self.view_header = ButtonGroup([
            self.month_button,
            self.week_button,
            self.day_button,
        ])
        view = api.user_settings.get('calendar-view', 'month')
        if view == 'basicDay':
            self.day_button.set_active(True)
        elif view == 'basicWeek':
            self.week_button.set_active(True)
        else:
            self.month_button.set_active(True)

        self.hbox.pack_start(self.nav_header, False, False, 6)
        self.hbox.pack_start(self.date_label, True, True, 6)
        self.hbox.pack_start(self.view_header, False, False, 6)
        self.main_vbox.show_all()
예제 #5
0
    def add_domain_header(self, options):
        if self.domain_header:
            self.header_bar.remove(self.domain_header)
            self.domain_header = None

        if not options:
            return

        buttons = []
        for (icon, label, action, in_header) in options:
            if not in_header:
                continue
            buttons.append(self.create_button(icon, action=action,
                                              tooltip=label))
        self.domain_header = ButtonGroup(buttons)
        self.domain_header.show_all()
        self.header_bar.pack_start(self.domain_header)
예제 #6
0
class ShellWindow(Delegate):
    """
    A Shell window is a

    - Window
    - Application box
    - Statusbar w/ Feedback button

    It contain common menu items for:
      - Signing out
      - Changing password
      - Closing the application
      - Printing
      - Editing user preferences
      - Spreedshet
      - Help menu (Chat, Content, Translation, Support, About)

    The main function is to create the common ui and switch between different
    applications.
    """
    app_title = _('Stoq')

    action_permissions = {}

    # FIXME: we should have a common structure for all information regarding
    # Actions
    common_action_permissions = {
        'HelpChat': ('app.common.help_chat', PermissionManager.PERM_ACCESS),
        'HelpTranslate':
        ('app.common.help_translate', PermissionManager.PERM_ACCESS),
        'HelpContents':
        ('app.common.help_contents', PermissionManager.PERM_ACCESS),
        'HelpSupport':
        ('app.common.help_support', PermissionManager.PERM_ACCESS),
        'HelpHelp': ('app.common.help', PermissionManager.PERM_ACCESS),
    }

    def __init__(self, options, shell, store, app):
        """Creates a new window

        :param options: optparse options
        :param shell: the shell
        :param store: a store
        :param app: a Gtk.Application instance
        """
        self._action_groups = {}
        self._help_section = None
        self._osx_app = None
        self.current_app = None
        self.shell = shell
        self.app = app
        self.in_ui_test = False
        self.options = options
        self.store = store
        self._pre_launcher_init()
        Delegate.__init__(self, toplevel=Gtk.ApplicationWindow.new(app))
        self._create_ui()
        self._launcher_ui_bootstrap()

    def _pre_launcher_init(self):
        if platform.system() == 'Darwin':
            import gtk_osxapplication
            self._osx_app = gtk_osxapplication.OSXApplication()
            self._osx_app.connect('NSApplicationBlockTermination',
                                  self._on_osx__block_termination)
            self._osx_app.set_use_quartz_accelerators(True)

        self._app_settings = api.user_settings.get('app-ui', {})
        self.main_vbox = Gtk.VBox()

    #
    # Private
    #

    def _create_application_actions(self):
        """Create the actions that activate the applications.

        This actions are prefixed by 'launch', followed by the app name (for
        instance launch.pos)
        """
        def callback(action, parameter, name):
            self.switch_application(name)

        group = Gio.SimpleActionGroup()
        self.toplevel.insert_action_group('launch', group)
        for app in self.get_available_applications():
            action = Gio.SimpleAction.new(app.name, None)
            action.connect('activate', callback, app.name)
            group.add_action(action)

        # Also add the launcher app
        action = Gio.SimpleAction.new('launcher', None)
        action.connect('activate', callback, 'launcher')
        group.add_action(action)

    def _create_menu2(self, actions):
        model = Gio.Menu()
        for action in actions or []:
            if isinstance(action, list):
                section = self._create_menu(action)
                model.append_section(None, section)
            else:
                label, name = action
                item = Gio.MenuItem.new(label, name)
                model.append_item(item)
        return model

    def _create_menu(self, actions):
        model = Gio.Menu()
        for action in actions or []:
            if isinstance(action, list):
                section = self._create_menu(action)
                model.append_section(None, section)
                pass
            else:
                fullname, icon, label, accel = self._action_specs[
                    action.get_name()][:-1]
                item = Gio.MenuItem.new(label, fullname)
                if accel:
                    item.set_attribute_value('accel', GLib.Variant('s', accel))
                    self.app.set_accels_for_action(fullname, [accel])
                model.append_item(item)
        return model

    def _create_shared_ui(self):
        self.toplevel.add(self.main_vbox)

        self.application_box = Gtk.HBox()
        self.main_vbox.pack_start(self.application_box, True, True, 0)
        self.application_box.show()

        self.stoq_menu = PopoverMenu(self)
        self.main_vbox.show_all()

        self.statusbar = self._create_statusbar()
        self.statusbar.set_visible(True)

        self.main_vbox.pack_start(self.statusbar, False, False, 0)

        self.main_vbox.set_focus_chain([self.application_box])
        self._create_application_actions()

    def _create_statusbar(self):
        statusbar = ShellStatusbar(self)

        # Set the initial text, the currently logged in user and the actual
        # branch and station.
        user = api.get_current_user(self.store)
        station = api.get_current_station(self.store)
        status_str = '   |   '.join([
            _("User: %s") % (user.get_description(), ),
            _("Computer: %s") % (station.name, ),
            "PID: %s" % (os.getpid(), )
        ])
        statusbar.push(0, status_str)
        return statusbar

    def _osx_setup_menus(self):
        self.quit.set_visible(False)
        self.HelpAbout.set_visible(False)
        self.HelpAbout.set_label(_('About Stoq'))
        self._osx_app.set_help_menu(self.HelpMenu.get_proxies()[0])
        self._osx_app.insert_app_menu_item(self.HelpAbout.get_proxies()[0], 0)
        self._osx_app.insert_app_menu_item(Gtk.SeparatorMenuItem(), 1)
        self.preferences.set_visible(False)
        self._osx_app.insert_app_menu_item(self.Preferences.get_proxies()[0],
                                           2)
        self._osx_app.ready()

    def _launcher_ui_bootstrap(self):
        self._restore_window_size()

        self.hide_app(empty=True)

        self._check_demo_mode()
        self._check_online_plugins()
        self._birthdays_bar = None
        self._check_client_birthdays()
        # json will restore tuples as lists. We need to convert them
        # to tuples or the comparison bellow won't work.
        actual_version = tuple(api.user_settings.get('actual-version', (0, )))
        if stoq.stoq_version > actual_version:
            api.user_settings.set('last-version-check', None)
            self._display_changelog_message()
            # Display the changelog message only once. Most users will never
            # click on the "See what's new" button, and that will affect its
            # visual identity.
            api.user_settings.set('actual-version', stoq.stoq_version)

        self._check_information()

        if not stoq.stable and not api.is_developer_mode():
            self._display_unstable_version_message()

        toplevel = self.get_toplevel()
        toplevel.connect('configure-event', self._on_toplevel__configure)
        toplevel.connect('delete-event', self._on_toplevel__delete_event)

        # A GtkWindowGroup controls grabs (blocking mouse/keyboard interaction),
        # by default all windows are added to the same window group.
        # We want to avoid setting modallity on other windows
        # when running a dialog using gtk_dialog_run/run_dialog.
        window_group = Gtk.WindowGroup()
        window_group.add_window(toplevel)

    def _check_online_plugins(self):
        # For each online plugin, try to download and install it.
        # Otherwise warn him
        online_plugins = InstalledPlugin.get_pre_plugin_names(self.store)
        if not online_plugins:
            return

        successes = []
        manager = get_plugin_manager()
        for plugin_name in online_plugins:
            success, msg = manager.download_plugin(plugin_name)
            successes.append(success)
            if success:
                manager.install_plugin(self.store, plugin_name)
                online_plugins.remove(plugin_name)

        if all(successes):
            return

        # Title
        title = _('You have pending plugins.')

        # Description
        url = 'https://stoq.link/?source=stoq-plugin-alert&hash={}'.format(
            api.sysparam.get_string('USER_HASH'))
        desc = _('The following plugins need to be enabled: <b>{}</b>.\n\n'
                 'You can do it by registering on <a href="{}">Stoq.link</a>.'
                 ).format(', '.join(online_plugins), url)
        msg = '<b>%s</b>\n%s' % (title, desc)
        self.add_info_bar(Gtk.MessageType.WARNING, msg)

    def _check_demo_mode(self):
        if not api.sysparam.get_bool('DEMO_MODE'):
            return

        if api.user_settings.get('hide-demo-warning'):
            return

        button_label = _('Use my own data')
        title = _('You are using Stoq with example data.')
        desc = (_("Some features are limited due to fiscal reasons. "
                  "Click on '%s' to remove the limitations.") % button_label)
        msg = '<b>%s</b>\n%s' % (api.escape(title), api.escape(desc))

        button = Gtk.Button(button_label)
        button.connect('clicked', self._on_enable_production__clicked)
        self.add_info_bar(Gtk.MessageType.WARNING, msg, action_widget=button)

    def _check_client_birthdays(self):
        if not api.sysparam.get_bool('BIRTHDAY_NOTIFICATION'):
            return

        # Display the info bar once per day
        date = api.user_settings.get('last-birthday-check')
        last_check = date and datetime.datetime.strptime(date,
                                                         '%Y-%m-%d').date()
        if last_check and last_check >= datetime.date.today():
            return

        # Only display the infobar if the user has access to calendar (because
        # clicking on the button will open it) and to sales (because it
        # requires that permission to be able to check client details)
        user = api.get_current_user(self.store)
        if not all([
                user.profile.check_app_permission(u'calendar'),
                user.profile.check_app_permission(u'sales')
        ]):
            return

        branch = api.get_current_branch(self.store)
        clients_count = ClientWithSalesView.find_by_birth_date(
            self.store, datetime.datetime.today(), branch=branch).count()

        if clients_count:
            msg = stoqlib_ngettext(
                _("There is %s client doing birthday today!"),
                _("There are %s clients doing birthday today!"),
                clients_count) % (clients_count, )
            button = Gtk.Button(_("Check the calendar"))
            button.connect('clicked', self._on_check_calendar__clicked)

            self._birthdays_bar = self.add_info_bar(
                Gtk.MessageType.INFO,
                "<b>%s</b>" % (GLib.markup_escape_text(msg), ),
                action_widget=button)

    def _check_information(self):
        """Check some information with Stoq Web API

        - Check if there are new versions of Stoq Available
        - Check if this Stoq Instance uses Stoq Link (and send data to us if
          it does).
        """
        # Check version
        self._version_checker = VersionChecker(self.store, self)
        self._version_checker.check_new_version()

    def _display_changelog_message(self):
        msg = _("Welcome to Stoq version %s!") % stoq.version

        button = Gtk.Button(_("See what's new"))
        button.connect('clicked', self._on_show_changelog__clicked)

        self._changelog_bar = self.add_info_bar(Gtk.MessageType.INFO,
                                                msg,
                                                action_widget=button)

    def _display_unstable_version_message(self):
        msg = _(
            'You are currently using an <b>unstable version</b> of Stoq that '
            'is under development,\nbe aware that it may behave incorrectly, '
            'crash or even loose your data.\n<b>Do not use in production.</b>')
        self.add_info_bar(Gtk.MessageType.WARNING, msg)

    def _save_window_size(self):
        if not hasattr(self, '_width'):
            return
        # Do not save the size of the window when we are in fullscreen
        window = self.get_toplevel()
        window = window.get_window()
        if window.get_state() & Gdk.WindowState.FULLSCREEN:
            return
        d = api.user_settings.get('launcher-geometry', {})
        d['width'] = str(self._width)
        d['height'] = str(self._height)
        d['x'] = str(self._x)
        d['y'] = str(self._y)

    def _restore_window_size(self):
        d = api.user_settings.get('launcher-geometry', {})
        try:
            width = int(d.get('width', -1))
            height = int(d.get('height', -1))
            x = int(d.get('x', -1))
            y = int(d.get('y', -1))
        except ValueError:
            pass

        # Setup the default window size, for smaller sizes use
        # 75% of the height or 600px if it's higher than 800px
        screen = Gdk.Screen.get_default()
        screen_height = screen.get_height()
        screen_width = screen.get_width()

        if height == -1 or y > screen_height:
            height = min(int(screen_height * 0.75), 650)

        if width == -1 or y > screen_width:
            width = min(int(screen_width * 0.75), 800)

        # Setup window position according to the settings file, but if settings file
        # indicates values out of the screen, move the window to the outermost position
        # within the screen.
        toplevel = self.get_toplevel()
        toplevel.set_default_size(width, height)
        y = min(y, screen_height - height)
        x = min(x, screen_width - width)
        if x != -1 and y != -1:
            toplevel.move(x, y)

    def _read_resource(self, domain, name):
        from stoqlib.lib.kiwilibrary import library

        # On development, documentation resources (e.g. COPYING) will
        # be located directly on the library's root
        devpath = os.path.join(library.get_root(), name)
        if os.path.exists(devpath):
            with open(devpath) as f:
                return f.read()

        return environ.get_resource_string('stoq', domain, name).decode()

    def _run_about(self):
        info = get_utility(IAppInfo)
        about = Gtk.AboutDialog()
        about.set_name(info.get("name"))
        about.set_version(info.get("version"))
        about.set_website(stoq.website)
        release_date = stoq.release_date
        about.set_comments(
            _('Release date: %s') %
            datetime.datetime(*release_date).strftime('%x'))
        about.set_copyright('Copyright (C) 2005-2012 Async Open Source')

        about.set_logo(render_logo_pixbuf('about'))

        # License

        if locale.getlocale()[0] == 'pt_BR':
            filename = 'COPYING.pt_BR'
        else:
            filename = 'COPYING'
        data = self._read_resource('docs', filename)
        about.set_license(data)

        # Authors & Contributors
        data = self._read_resource('docs', 'AUTHORS')
        lines = data.split('\n')
        lines.append('')  # separate authors from contributors
        data = self._read_resource('docs', 'CONTRIBUTORS')
        lines.extend([c.strip() for c in data.split('\n')])
        about.set_authors(lines)

        about.set_transient_for(get_current_toplevel())
        about.run()
        about.destroy()

    def _hide_current_application(self):
        if not self.current_app:
            return False

        if self._shutdown_application():
            self.hide_app()
        return True

    def _show_uri(self, uri):
        toplevel = self.get_toplevel()
        open_browser(uri, toplevel.get_screen())

    def _empty_message_area(self):
        area = self.statusbar.message_area
        for child in area.get_children()[1:]:
            child.destroy()

    def _shutdown_application(self, restart=False, force=False):
        """Shutdown the application:
        There are 3 possible outcomes of calling this function, depending
        on how many windows and applications are open::

        * Hide application, save state, show launcher
        * Close window, save state, show launcher
        * Close window, save global state, terminate application

        This function is called in the following situations:
        * When closing a window (delete-event)
        * When clicking File->Close in an application
        * When clicking File->Quit in the launcher
        * When clicking enable production mode (restart=True)
        * Pressing Ctrl-w/F5 in an application
        * Pressing Ctrl-q in the launcher
        * Pressing Alt-F4 on Win32
        * Pressing Cmd-q on Mac

        :returns: True if shutdown was successful, False if not
        """
        log.debug("Shutting down launcher")

        # Ask the application if we can close, currently this only happens
        # when trying to close the POS app if there's a sale in progress
        current_app = self.current_app
        if current_app and not current_app.can_close_application():
            return False

        # We can currently only close a window if the currently active
        # application is the launcher application, unless we force it
        # (e.g. when enabling production mode)
        if current_app and current_app.app_name != 'launcher' and not force:
            return True

        # Here we save app specific state such as object list
        # column position/ordering
        if current_app and current_app.search:
            current_app.search.save_columns()

        self._save_window_size()

        self.shell.close_window(self)

        # If there are other windows open, do not terminate the application, just
        # close the current window and leave the others alone
        if self.shell.windows:
            return True

        self.shell.quit(restart=restart)

    def _create_ui(self):
        if self._osx_app:
            self._osx_setup_menus()

        self._create_headerbar()
        self._create_actions()
        self._create_shared_ui()

        toplevel = self.get_toplevel().get_toplevel()
        add_current_toplevel(toplevel)

        if self.options.debug:
            self.add_debug_ui()

    def create_button(self,
                      icon,
                      label=None,
                      menu_model=None,
                      menu=False,
                      action=None,
                      tooltip=None,
                      style_class=None,
                      toggle=False,
                      icon_size=Gtk.IconSize.BUTTON):
        if menu_model or menu:
            button = Gtk.MenuButton()
        elif toggle:
            button = Gtk.ToggleButton()
        else:
            button = Gtk.Button()

        box = Gtk.HBox(spacing=6)
        button.add(box)

        if icon:
            image = Gtk.Image.new_from_icon_name(icon, icon_size)
            box.pack_start(image, False, False, 0)
        if label:
            label = Gtk.Label.new(label)
            box.pack_start(label, False, False, 0)

        if menu_model:
            button.set_menu_model(menu_model)
        if action:
            button.set_action_name(action)
        if tooltip:
            button.set_tooltip_text(tooltip)
        if style_class:
            button.get_style_context().add_class(style_class)
        return button

    def _create_headerbar(self):
        # User/help menu
        user = api.get_current_user(self.store)
        xml = MENU_XML.format(username=api.escape(user.get_description()),
                              preferences=_('Preferences...'),
                              password=_('Change password...'),
                              signout=_('Sign out...'),
                              help=_('Help'),
                              contents=_('Contents'),
                              translate=_('Translate Stoq...'),
                              get_support=_('Get support online...'),
                              chat=_('Online chat...'),
                              about=_('About'),
                              quit=_('Quit'))
        builder = Gtk.Builder.new_from_string(xml, -1)

        # Header bar
        self.header_bar = Gtk.HeaderBar()
        self.toplevel.set_titlebar(self.header_bar)

        # Right side
        self.close_btn = self.create_button('fa-power-off-symbolic',
                                            action='stoq.quit')
        self.close_btn.set_relief(Gtk.ReliefStyle.NONE)
        self.min_btn = self.create_button('fa-window-minimize-symbolic')
        self.min_btn.set_relief(Gtk.ReliefStyle.NONE)
        #self.header_bar.pack_end(self.close_btn)
        #self.header_bar.pack_end(self.min_btn)
        box = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        box.pack_start(self.min_btn, False, False, 0)
        box.pack_start(self.close_btn, False, False, 0)
        self.header_bar.pack_end(box)

        self.user_menu = builder.get_object('app-menu')
        self.help_section = builder.get_object('help-section')
        self.user_button = self.create_button('fa-cog-symbolic',
                                              menu_model=self.user_menu)
        self.search_menu = Gio.Menu()
        self.search_button = self.create_button('fa-search-symbolic',
                                                _('Searches'),
                                                menu_model=self.search_menu)
        self.main_menu = Gio.Menu()
        self.menu_button = self.create_button('fa-bars-symbolic',
                                              _('Actions'),
                                              menu_model=self.main_menu)

        self.header_bar.pack_end(
            ButtonGroup(
                [self.menu_button, self.search_button, self.user_button]))

        self.sign_button = self.create_button('',
                                              _('Sign now'),
                                              style_class='suggested-action')
        #self.header_bar.pack_end(self.sign_button)

        # Left side
        self.home_button = self.create_button(STOQ_LAUNCHER,
                                              style_class='suggested-action')
        self.new_menu = Gio.Menu()
        self.new_button = self.create_button('fa-plus-symbolic',
                                             _('New'),
                                             menu_model=self.new_menu)

        self.header_bar.pack_start(
            ButtonGroup([
                self.home_button,
                self.new_button,
            ]))

        self.domain_header = None
        self.header_bar.show_all()

        self.notifications = NotificationCounter(self.home_button, blink=True)

    def _create_actions(self):
        # Gloabl actions avaiable at any time from all applications
        actions = [
            ('preferences', None),
            ('export', None),
            ('print', None),
            ('sign_out', None),
            ('change_password', None),
            ('HelpApp', None),
            ('HelpContents', None),
            ('HelpTranslate', None),
            ('HelpSupport', None),
            ('HelpChat', None),
            ('HelpAbout', None),
            ('quit', None),
        ]

        pm = PermissionManager.get_permission_manager()
        group = Gio.SimpleActionGroup()
        self.toplevel.insert_action_group('stoq', group)
        for (name, param_type) in actions:
            action = Gio.SimpleAction.new(name, param_type)
            group.add_action(action)
            # Save the action in self so that auto signal connections work
            setattr(self, name, action)

            # Check permissions
            key, required = self.action_permissions.get(
                name, (None, pm.PERM_ALL))
            if not pm.get(key) & required:
                action.set_enabled(False)

    def _load_shell_app(self, app_name):
        user = api.get_current_user(self.store)

        # FIXME: Move over to domain
        if (app_name != 'launcher'
                and not user.profile.check_app_permission(app_name)):
            error(
                _("This user lacks credentials \nfor application %s") %
                app_name)
            return None
        module = __import__("stoq.gui.%s" % (app_name, ), globals(), locals(),
                            [''])
        attribute = app_name.capitalize() + 'App'
        shell_app_class = getattr(module, attribute, None)
        if shell_app_class is None:
            raise SystemExit("%s app misses a %r attribute" %
                             (app_name, attribute))

        shell_app_class.app_name = app_name
        shell_app = shell_app_class(window=self, store=self.store)

        return shell_app

    def _wrap_action_callback(self, app, name):
        """Wraps a Gtk.Action callback to a Gio.SimpleAction callback.

        This is just a temporary wrapper untill all apps are properly migrated
        to the new gtk api.
        """
        method_name = 'on_%s__activate' % name
        try:
            old_callback = getattr(app, method_name)
        except AttributeError:
            return

        def new_callback(action, parameter):
            return old_callback(action)

        setattr(app, method_name, new_callback)

    #
    # Public API
    #

    def add_ui_actions(self, app, actions, name):
        for spec in actions:
            spec = list(spec)
            act_name, icon, label = spec[:3]
            accel, long_desc, callback = None, None, None
            spec[:3] = []
            if spec:
                accel = spec.pop(0)
            if spec:
                long_desc = spec.pop(0)
            if spec:
                callback = spec.pop(0)

            param_type = None
            if name == 'Actions':
                action = Gio.SimpleAction.new(act_name, param_type)
                self._wrap_action_callback(app, act_name)
            elif name in ('ToggleActions', 'RadioActions'):
                action = Gio.SimpleAction.new_stateful(
                    act_name, param_type, GLib.Variant.new_boolean(False))

                def set_active(value):
                    # TODO: colocar deprecation warning aqui.
                    action.set_state(GLib.Variant.new_boolean(value))
                    # XXX: The change-state event is not being emitted
                    method_name = 'on_%s__activate' % act_name
                    getattr(app, method_name)(action, None)

                def get_active():
                    value = action.get_state().get_boolean()
                    return value

                action.set_active = set_active
                action.get_active = get_active

            app.action_group.add_action(action)
            app.window._action_specs[act_name] = (app.app_name + '.' +
                                                  act_name, icon, label, accel,
                                                  long_desc)
            # Save the action in the app so that auto signal connections work
            setattr(app, act_name, action)

            if callback:
                action.connect('activate', callback)

    def set_close_button_icon(self, icon_name):
        image = self.close_btn.get_child().get_children()[0]
        image.set_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
        image.set_size_request(16, 16)

    def show_app(self, app, app_window, **params):
        self.stoq_menu.set_visible(False)
        app_window.get_parent().remove(app_window)
        self.application_box.add(app_window)
        self.application_box.set_child_packing(app_window, True, True, 0,
                                               Gtk.PackType.START)

        self._current_app_settings = self._app_settings.setdefault(
            app.app_name, {})

        self.header_bar.set_title(app.get_title())
        self.header_bar.set_subtitle(app.app_title)
        self.application_box.show()
        app.toplevel = self.get_toplevel()

        if self._birthdays_bar is not None:
            if app.app_name in ['launcher', 'sales']:
                self._birthdays_bar.show()
            else:
                self._birthdays_bar.hide()

        if app.app_name == 'launcher':
            icon_name = 'fa-power-off-symbolic'
        else:
            icon_name = 'fa-chevron-left-symbolic'
        self.set_close_button_icon(icon_name)

        # StartApplicationEvent must be emitted before calling app.activate(),
        # so that the plugins can have the chance to modify the application
        # before any other event is emitted.
        StartApplicationEvent.emit(app.app_name, app)
        app.activate(**params)

        self.current_app = app
        self.current_widget = app_window

        if not self.in_ui_test:
            while Gtk.events_pending():
                Gtk.main_iteration()
            app_window.show()
        app.setup_focus()

    def hide_app(self, empty=False):
        """
        Hide the current application in this window

        :param bool empty: if ``True``, do not add the default launcher application
        """
        self.application_box.hide()
        # Reset menus/headerbar
        self.main_menu.remove_all()
        self.search_menu.remove_all()
        self.new_menu.remove_all()

        if self.current_app:
            inventory_bar = getattr(self.current_app, 'inventory_bar', None)
            if inventory_bar:
                inventory_bar.hide()
            if self.current_app.search:
                self.current_app.search.save_columns()
            self.current_app.deactivate()

            # We need to remove the accels for this app, otherwise they would
            # still be active from other applications
            for spec in self._action_specs.values():
                fullname, icon, label, accel = spec[:-1]
                if accel:
                    self.app.set_accels_for_action(fullname, [])

            if self._help_section:
                self.help_section.remove(0)
                self._help_section = None
            self.current_widget.destroy()

            StopApplicationEvent.emit(self.current_app.app_name,
                                      self.current_app)
            self.current_app = None

        self._empty_message_area()
        if not empty:
            self.run_application(app_name=u'launcher')

    def add_info_bar(self, message_type, label, action_widget=None):
        """Show an information bar to the user.

        :param message_type: message type, a Gtk.MessageType
        :param label: label to display
        :param action_widget: optional, most likely a button
        :returns: the infobar
        """
        infobar = MessageBar(label, message_type)

        if action_widget:
            infobar.add_action_widget(action_widget, 0)
            action_widget.show()
        infobar.show()

        self.main_vbox.pack_start(infobar, False, False, 0)
        self.main_vbox.reorder_child(infobar, 0)
        return infobar

    def set_help_section(self, label, section):
        self._help_section = section
        self.help_section.insert(0, label, 'stoq.HelpApp')

    def add_debug_ui(self):
        self.toplevel.set_interactive_debugging(True)
        return
        actions = [
            ('Introspect', None, _('Introspect slaves')),
            ('RemoveSettingsCache', None, _('Remove settings cache')),
        ]

        self.add_ui_actions(self, actions, 'Actions')
        self.add_extra_items([self.Introspect, self.RemoveSettingsCache],
                             _('Debug'))

    def add_domain_header(self, options):
        if self.domain_header:
            self.header_bar.remove(self.domain_header)
            self.domain_header = None

        if not options:
            return

        buttons = []
        for (icon, label, action, in_header) in options:
            if not in_header:
                continue
            buttons.append(
                self.create_button(icon, action=action, tooltip=label))
        self.domain_header = ButtonGroup(buttons)
        self.domain_header.show_all()
        self.header_bar.pack_start(self.domain_header)

    def add_new_items(self, actions, label=None):
        self.new_menu.append_section(label, self._create_menu(actions))

    def add_new_items2(self, actions, label=None):
        self.new_menu.append_section(label, self._create_menu2(actions))

    def add_export_items(self, actions=None):
        self._export_menu = self._create_menu(actions)
        self._export_menu.insert(0, _('Export to spreadsheet...'),
                                 'stoq.export')
        self.main_menu.append_section(None, self._export_menu)

    def add_print_items(self, actions=None):
        self._print_menu = self._create_menu(actions)
        self._print_menu.insert(0, _('Print this report...'), 'stoq.print')
        self.main_menu.append_section(None, self._print_menu)

    def add_print_items2(self, actions=None):
        self._print_menu = self._create_menu2(actions)
        self._print_menu.insert(0, _('Print this report...'), 'stoq.print')
        self.main_menu.append_section(None, self._print_menu)

    def add_extra_items(self, actions=None, label=None):
        self._extra_items = self._create_menu(actions)
        self.main_menu.append_section(label, self._extra_items)

    def add_extra_items2(self, actions=None, label=None):
        self._extra_items = self._create_menu2(actions)
        self.main_menu.append_section(label, self._extra_items)

    def add_search_items(self, actions, label=None):
        self.search_menu.append_section(label, self._create_menu(actions))

    def close(self):
        """
        Closes this window
        """
        self.hide_app(empty=True)
        self.toplevel.destroy()
        self.hide()

    def switch_application(self, app_name, **params):
        params['hide'] = True
        self.run_application(app_name, **params)

    def run_application(self, app_name, **params):
        """
        Load and show an application in a shell window.

        :param ShellWindow shell_window: shell window to run application in
        :param str appname: the name of the application to run
        :param params: extra arguments passed to the application
        :returns: the shell application or ``None`` if the user doesn't have
          access to open the application
        :rtype: ShellApp
        """
        # FIXME: Maybe we should really have an app that would be responsible
        # for doing administration tasks related to stoqlink here? Right now
        # we are only going to open the stoq.link url
        if app_name == 'link':
            toplevel = self.get_toplevel()
            user_hash = api.sysparam.get_string('USER_HASH')
            url = 'https://stoq.link?source=stoq&hash={}'.format(user_hash)
            open_browser(url, toplevel.get_screen())
            return

        if params.pop('hide', False):
            self.hide_app(empty=True)

        shell_app = self._load_shell_app(app_name)
        if shell_app is None:
            return None

        # Set the icon for the application
        app_icon = get_application_icon(app_name)
        toplevel = self.get_toplevel()
        toplevel.set_icon_name(app_icon)

        # FIXME: We should remove the toplevel windows of all ShellApp's
        #        glade files, as we don't use them any longer.
        shell_app_window = shell_app.get_toplevel()
        self.show_app(shell_app, shell_app_window.get_child(), **params)
        shell_app_window.hide()

        return shell_app

    def get_available_applications(self):
        user = api.get_current_user(self.store)

        permissions = user.profile.get_permissions()
        descriptions = get_utility(IApplicationDescriptions).get_descriptions()

        available_applications = []

        # sorting by app_full_name
        for name, full, icon, descr in locale_sorted(
                descriptions, key=operator.itemgetter(1)):
            if permissions.get(name):
                available_applications.append(
                    Application(name, full, icon, descr))
        return available_applications

    #
    # Kiwi callbacks
    #

    def key_F5(self):
        self.switch_application('launcher')
        return True

    def _on_osx__block_termination(self, app):
        return not self._shutdown_application()

    def _on_show_changelog__clicked(self, button):
        show_section('changelog')
        self._changelog_bar.hide()

    def _on_check_calendar__clicked(self, button):
        self.switch_application(u'calendar')
        api.user_settings.set('last-birthday-check',
                              datetime.date.today().strftime('%Y-%m-%d'))
        self._birthdays_bar.hide()
        self._birthdays_bar = None

    def _on_toplevel__configure(self, widget, event):
        window = widget.get_window()
        rect = window.get_frame_extents()
        self._x = rect.x
        self._y = rect.y
        self._width = event.width
        self._height = event.height

    def _on_toplevel__delete_event(self, *args):
        if self._hide_current_application():
            return True

        self._shutdown_application()

    def _on_enable_production__clicked(self, button):
        if not self.current_app.can_close_application():
            return
        if not yesno(
                _(u"This will enable production mode and finish the "
                  u"demonstration. Are you sure?"), Gtk.ResponseType.NO,
                _(u"Enable production mode"), _(u"Continue testing")):
            return

        api.config.set('Database', 'enable_production', 'True')
        api.config.flush()
        self._shutdown_application(restart=True, force=True)

    def on_min_btn__clicked(self, button):
        self.get_toplevel().iconify()

    # File

    def on_SearchToolItem__activate(self, action):
        if self.current_app:
            self.current_app.search_activate()

    def on_print__activate(self, action, parameter):
        self.current_app.print_activate()

    def on_export__activate(self, action, parameter):
        self.current_app.export_spreadsheet_activate()

    def on_change_password__activate(self, action, parameter):
        from stoq.lib.gui.slaves.userslave import PasswordEditor
        store = api.new_store()
        user = api.get_current_user(store)
        retval = run_dialog(PasswordEditor, self, store, user)
        store.confirm(retval)
        store.close()

    def on_sign_out__activate(self, action, parameter):
        from stoqlib.lib.interfaces import ICookieFile
        get_utility(ICookieFile).clear()
        self._shutdown_application(restart=True)

    def on_quit__activate(self, action, parameter):
        if self._hide_current_application():
            return

        self._shutdown_application()
        self.get_toplevel().destroy()

    def on_home_button__clicked(self, action):
        self.stoq_menu.toggle()

    # View

    def on_preferences__activate(self, action, parameter):
        with api.new_store() as store:
            run_dialog(PreferencesEditor, self, store)

    # Help

    def on_HelpApp__activate(self, action, parameter):
        show_section(self._help_section)

    def on_HelpContents__activate(self, action, parameter):
        show_contents()

    def on_HelpTranslate__activate(self, action, parameter):
        self._show_uri("https://www.transifex.com/projects/p/stoq")

    def on_HelpChat__activate(self, action, parameter):
        self._show_uri("http://www.stoq.com.br/")

    def on_HelpSupport__activate(self, action, parameter):
        self._show_uri("http://www.stoq.com.br/suporte")

    def on_HelpAbout__activate(self, action, parameter):
        self._run_about()

    def on_main_menu__items_changed(self, menu, position, removed, added):
        self.menu_button.set_sensitive(menu.get_n_items() > 0)

    def on_search_menu__items_changed(self, menu, position, removed, added):
        self.search_button.set_sensitive(menu.get_n_items() > 0)

    def on_new_menu__items_changed(self, menu, position, removed, added):
        self.new_button.set_sensitive(menu.get_n_items() > 0)

    # Debug

    def on_Introspect__activate(self, action):
        window = self.get_toplevel()
        introspect_slaves(window)

    def on_RemoveSettingsCache__activate(self, action):
        keys = ['app-ui', 'launcher-geometry']
        keys.append('search-columns-%s' %
                    (api.get_current_user(api.get_default_store()).username, ))

        for key in keys:
            try:
                api.user_settings.remove(key)
            except KeyError:
                pass
예제 #7
0
class CalendarApp(ShellApp):

    app_title = _('Calendar')
    gladefile = 'calendar'

    def __init__(self, window, store=None):
        # Create this here because CalendarView will update it.
        # It will only be shown on create_ui though
        self.date_label = Gtk.Label(label='')
        self._calendar = CalendarView(self)
        ShellApp.__init__(self, window, store=store)
        threadit(self._setup_daemon)

    def _setup_daemon(self):
        daemon = start_daemon()
        assert daemon.running
        self._calendar.set_daemon_uri(daemon.server_uri)
        schedule_in_main_thread(self._calendar.load)

    #
    # ShellApp overrides
    #

    def create_actions(self):
        group = get_accels('app.calendar')
        actions = [
            # File
            ('NewClientCall', None, _("Client call"),
             group.get('new_client_call'), _("Add a new client call")),
            ('NewPayable', None, _("Account payable"),
             group.get('new_payable'), _("Add a new account payable")),
            ('NewReceivable', None, _("Account receivable"),
             group.get('new_receivable'), _("Add a new account receivable")),
            ('NewWorkOrder', None, _("Work order"),
             group.get('new_work_order'), _("Add a new work order")),
            # View
            ('Back', Gtk.STOCK_GO_BACK, _("Back"),
             group.get('go_back'), _("Go back")),
            ('Forward', Gtk.STOCK_GO_FORWARD, _("Forward"),
             group.get('go_forward'), _("Go forward")),
            ('Today', None, _("Show today"),
             group.get('show_today'), _("Show today")),
        ]
        self.calendar_ui = self.add_ui_actions(actions)
        self.set_help_section(_("Calendar help"), 'app-calendar')

        toggle_actions = [
            ('AccountsPayableEvents', None, _("Accounts payable"),
             None, _("Show accounts payable in the list")),
            ('AccountsReceivableEvents', None, _("Accounts receivable"),
             None, _("Show accounts receivable in the list")),
            ('PurchaseEvents', None, _("Purchases"),
             None, _("Show purchases in the list")),
            ('ClientCallEvents', None, _("Client Calls"),
             None, _("Show client calls in the list")),
            ('ClientBirthdaysEvents', None, _("Client Birthdays"),
             None, _("Show client birthdays in the list")),
            ('WorkOrderEvents', None, _("Work orders"),
             None, _("Show work orders in the list")),
        ]
        self.add_ui_actions(toggle_actions, 'ToggleActions')

        events_info = dict(
            in_payments=(self.AccountsReceivableEvents, self.NewReceivable,
                         u'receivable'),
            out_payments=(self.AccountsPayableEvents, self.NewPayable,
                          u'payable'),
            purchase_orders=(self.PurchaseEvents, None, u'stock'),
            client_calls=(self.ClientCallEvents, self.NewClientCall, u'sales'),
            client_birthdays=(self.ClientBirthdaysEvents, None, u'sales'),
            work_orders=(self.WorkOrderEvents, self.NewWorkOrder, u'services'),
        )

        user = api.get_current_user(self.store)
        events = self._calendar.get_events()
        for event_name, value in events_info.items():
            view_action, new_action, app = value
            view_action.set_state(GLib.Variant.new_boolean(events[event_name]))

            # Disable feature if user does not have acces to required
            # application
            if not user.profile.check_app_permission(app):
                view_action.set_state(GLib.Variant.new_boolean(False))
                self.set_sensitive([view_action], False)
                if new_action:
                    self.set_sensitive([new_action], False)

            view_action.connect('change-state', self._view_option_state_changed)
        self._update_events()

    def create_ui(self):
        self.window.add_extra_items([
            self.ClientCallEvents,
            self.ClientBirthdaysEvents,
            self.AccountsPayableEvents,
            self.AccountsReceivableEvents,
            self.PurchaseEvents,
            self.WorkOrderEvents
        ], label=_('View'))
        self.window.add_new_items([self.NewClientCall,
                                   self.NewPayable,
                                   self.NewReceivable, self.NewWorkOrder])

        # Reparent the toolbar, to show the date next to it.
        self.hbox = Gtk.HBox()

        self.date_label.set_xalign(0)

        self.main_vbox.pack_start(self.hbox, False, False, 6)
        self.main_vbox.pack_start(self._calendar, True, True, 0)

        self.nav_header = ButtonGroup([
            self.window.create_button('fa-arrow-left-symbolic', action='calendar.Back'),
            self.window.create_button('fa-calendar-symbolic',
                                      action='calendar.Today',
                                      tooltip=_('Today')),
            self.window.create_button('fa-arrow-right-symbolic', action='calendar.Forward')
        ])

        self.month_button = Gtk.ToggleButton.new_with_label(_('Month'))
        self.week_button = Gtk.ToggleButton.new_with_label(_('Week'))
        self.day_button = Gtk.ToggleButton.new_with_label(_('Day'))
        self.view_header = ButtonGroup([
            self.month_button,
            self.week_button,
            self.day_button,
        ])
        view = api.user_settings.get('calendar-view', 'month')
        if view == 'basicDay':
            self.day_button.set_active(True)
        elif view == 'basicWeek':
            self.week_button.set_active(True)
        else:
            self.month_button.set_active(True)

        self.hbox.pack_start(self.nav_header, False, False, 6)
        self.hbox.pack_start(self.date_label, True, True, 6)
        self.hbox.pack_start(self.view_header, False, False, 6)
        self.main_vbox.show_all()

    def deactivate(self):
        self.window.header_bar.remove(self.nav_header)

    # Private

    def _update_events(self, *args):
        self._calendar.update_events(
            out_payments=self.AccountsPayableEvents.get_state().get_boolean(),
            in_payments=self.AccountsReceivableEvents.get_state().get_boolean(),
            purchase_orders=self.PurchaseEvents.get_state().get_boolean(),
            client_calls=self.ClientCallEvents.get_state().get_boolean(),
            client_birthdays=self.ClientBirthdaysEvents.get_state().get_boolean(),
            work_orders=self.WorkOrderEvents.get_state().get_boolean(),
        )

    def _new_client_call(self):
        with api.new_store() as store:
            self.run_dialog(CallsEditor, store, None, None, Client)

        if store.committed:
            self._update_events()

    def _new_work_order(self):
        with api.new_store() as store:
            self.run_dialog(WorkOrderEditor, store)

        if store.committed:
            self._update_events()

    def _new_payment(self, editor):
        with api.new_store() as store:
            self.run_dialog(editor, store)

        if store.committed:
            self._update_events()

    #
    # Kiwi callbacks
    #

    def _view_option_state_changed(self, action, value):
        action.set_state(value)
        self._update_events()

    # Toolbar

    def on_NewClientCall__activate(self, action):
        self._new_client_call()

    def on_NewPayable__activate(self, action):
        self._new_payment(OutPaymentEditor)

    def on_NewReceivable__activate(self, action):
        self._new_payment(InPaymentEditor)

    def on_NewWorkOrder__activate(self, action):
        self._new_work_order()

    def on_Back__activate(self, action):
        self._calendar.go_prev()

    def on_Today__activate(self, action):
        self._calendar.show_today()

    def on_Forward__activate(self, action):
        self._calendar.go_next()

    def _update_view_buttons(self, view):
        views = ['month', 'basicWeek', 'basicDay']
        self._calendar.change_view(view)
        children = self.view_header.get_children()
        index = views.index(view)
        for i, widget in enumerate(children):
            widget.set_active(i == index)

    def on_month_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('month')

    def on_week_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('basicWeek')

    def on_day_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('basicDay')
예제 #8
0
파일: calendar.py 프로젝트: 5l1v3r1/stoq-1
class CalendarApp(ShellApp):

    app_title = _('Calendar')
    gladefile = 'calendar'

    def __init__(self, window, store=None):
        # Create this here because CalendarView will update it.
        # It will only be shown on create_ui though
        self.date_label = Gtk.Label(label='')
        self._calendar = CalendarView(self)
        ShellApp.__init__(self, window, store=store)
        threadit(self._setup_daemon)

    def _setup_daemon(self):
        daemon = start_daemon()
        assert daemon.running
        self._calendar.set_daemon_uri(daemon.server_uri)
        schedule_in_main_thread(self._calendar.load)

    #
    # ShellApp overrides
    #

    def create_actions(self):
        group = get_accels('app.calendar')
        actions = [
            # File
            ('NewClientCall', None, _("Client call"),
             group.get('new_client_call'), _("Add a new client call")),
            ('NewPayable', None, _("Account payable"),
             group.get('new_payable'), _("Add a new account payable")),
            ('NewReceivable', None, _("Account receivable"),
             group.get('new_receivable'), _("Add a new account receivable")),
            ('NewWorkOrder', None, _("Work order"),
             group.get('new_work_order'), _("Add a new work order")),
            # View
            ('Back', Gtk.STOCK_GO_BACK, _("Back"), group.get('go_back'),
             _("Go back")),
            ('Forward', Gtk.STOCK_GO_FORWARD, _("Forward"),
             group.get('go_forward'), _("Go forward")),
            ('Today', None, _("Show today"), group.get('show_today'),
             _("Show today")),
        ]
        self.calendar_ui = self.add_ui_actions(actions)
        self.set_help_section(_("Calendar help"), 'app-calendar')

        toggle_actions = [
            ('AccountsPayableEvents', None, _("Accounts payable"), None,
             _("Show accounts payable in the list")),
            ('AccountsReceivableEvents', None, _("Accounts receivable"), None,
             _("Show accounts receivable in the list")),
            ('PurchaseEvents', None, _("Purchases"), None,
             _("Show purchases in the list")),
            ('ClientCallEvents', None, _("Client Calls"), None,
             _("Show client calls in the list")),
            ('ClientBirthdaysEvents', None, _("Client Birthdays"), None,
             _("Show client birthdays in the list")),
            ('WorkOrderEvents', None, _("Work orders"), None,
             _("Show work orders in the list")),
        ]
        self.add_ui_actions(toggle_actions, 'ToggleActions')

        events_info = dict(
            in_payments=(self.AccountsReceivableEvents, self.NewReceivable,
                         u'receivable'),
            out_payments=(self.AccountsPayableEvents, self.NewPayable,
                          u'payable'),
            purchase_orders=(self.PurchaseEvents, None, u'stock'),
            client_calls=(self.ClientCallEvents, self.NewClientCall, u'sales'),
            client_birthdays=(self.ClientBirthdaysEvents, None, u'sales'),
            work_orders=(self.WorkOrderEvents, self.NewWorkOrder, u'services'),
        )

        user = api.get_current_user(self.store)
        events = self._calendar.get_events()
        for event_name, value in events_info.items():
            view_action, new_action, app = value
            view_action.set_state(GLib.Variant.new_boolean(events[event_name]))

            # Disable feature if user does not have acces to required
            # application
            if not user.profile.check_app_permission(app):
                view_action.set_state(GLib.Variant.new_boolean(False))
                self.set_sensitive([view_action], False)
                if new_action:
                    self.set_sensitive([new_action], False)

            view_action.connect('change-state',
                                self._view_option_state_changed)
        self._update_events()

    def create_ui(self):
        self.window.add_extra_items([
            self.ClientCallEvents, self.ClientBirthdaysEvents,
            self.AccountsPayableEvents, self.AccountsReceivableEvents,
            self.PurchaseEvents, self.WorkOrderEvents
        ],
                                    label=_('View'))
        self.window.add_new_items([
            self.NewClientCall, self.NewPayable, self.NewReceivable,
            self.NewWorkOrder
        ])

        # Reparent the toolbar, to show the date next to it.
        self.hbox = Gtk.HBox()

        self.date_label.set_xalign(0)

        self.main_vbox.pack_start(self.hbox, False, False, 6)
        self.main_vbox.pack_start(self._calendar, True, True, 0)

        self.nav_header = ButtonGroup([
            self.window.create_button('fa-arrow-left-symbolic',
                                      action='calendar.Back'),
            self.window.create_button('fa-calendar-symbolic',
                                      action='calendar.Today',
                                      tooltip=_('Today')),
            self.window.create_button('fa-arrow-right-symbolic',
                                      action='calendar.Forward')
        ])

        self.month_button = Gtk.ToggleButton.new_with_label(_('Month'))
        self.week_button = Gtk.ToggleButton.new_with_label(_('Week'))
        self.day_button = Gtk.ToggleButton.new_with_label(_('Day'))
        self.view_header = ButtonGroup([
            self.month_button,
            self.week_button,
            self.day_button,
        ])
        view = api.user_settings.get('calendar-view', 'month')
        if view == 'basicDay':
            self.day_button.set_active(True)
        elif view == 'basicWeek':
            self.week_button.set_active(True)
        else:
            self.month_button.set_active(True)

        self.hbox.pack_start(self.nav_header, False, False, 6)
        self.hbox.pack_start(self.date_label, True, True, 6)
        self.hbox.pack_start(self.view_header, False, False, 6)
        self.main_vbox.show_all()

    def deactivate(self):
        self.window.header_bar.remove(self.nav_header)

    # Private

    def _update_events(self, *args):
        self._calendar.update_events(
            out_payments=self.AccountsPayableEvents.get_state().get_boolean(),
            in_payments=self.AccountsReceivableEvents.get_state().get_boolean(
            ),
            purchase_orders=self.PurchaseEvents.get_state().get_boolean(),
            client_calls=self.ClientCallEvents.get_state().get_boolean(),
            client_birthdays=self.ClientBirthdaysEvents.get_state().
            get_boolean(),
            work_orders=self.WorkOrderEvents.get_state().get_boolean(),
        )

    def _new_client_call(self):
        with api.new_store() as store:
            self.run_dialog(CallsEditor, store, None, None, Client)

        if store.committed:
            self._update_events()

    def _new_work_order(self):
        with api.new_store() as store:
            self.run_dialog(WorkOrderEditor, store)

        if store.committed:
            self._update_events()

    def _new_payment(self, editor):
        with api.new_store() as store:
            self.run_dialog(editor, store)

        if store.committed:
            self._update_events()

    #
    # Kiwi callbacks
    #

    def _view_option_state_changed(self, action, value):
        action.set_state(value)
        self._update_events()

    # Toolbar

    def on_NewClientCall__activate(self, action):
        self._new_client_call()

    def on_NewPayable__activate(self, action):
        self._new_payment(OutPaymentEditor)

    def on_NewReceivable__activate(self, action):
        self._new_payment(InPaymentEditor)

    def on_NewWorkOrder__activate(self, action):
        self._new_work_order()

    def on_Back__activate(self, action):
        self._calendar.go_prev()

    def on_Today__activate(self, action):
        self._calendar.show_today()

    def on_Forward__activate(self, action):
        self._calendar.go_next()

    def _update_view_buttons(self, view):
        views = ['month', 'basicWeek', 'basicDay']
        self._calendar.change_view(view)
        children = self.view_header.get_children()
        index = views.index(view)
        for i, widget in enumerate(children):
            widget.set_active(i == index)

    def on_month_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('month')

    def on_week_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('basicWeek')

    def on_day_button__toggled(self, button):
        if button.get_active():
            self._update_view_buttons('basicDay')
예제 #9
0
class ShellWindow(Delegate):
    """
    A Shell window is a

    - Window
    - Application box
    - Statusbar w/ Feedback button

    It contain common menu items for:
      - Signing out
      - Changing password
      - Closing the application
      - Printing
      - Editing user preferences
      - Spreedshet
      - Help menu (Chat, Content, Translation, Support, About)

    The main function is to create the common ui and switch between different
    applications.
    """
    app_title = _('Stoq')

    action_permissions = {}

    # FIXME: we should have a common structure for all information regarding
    # Actions
    common_action_permissions = {
        'HelpChat': ('app.common.help_chat', PermissionManager.PERM_ACCESS),
        'HelpTranslate': ('app.common.help_translate', PermissionManager.PERM_ACCESS),
        'HelpContents': ('app.common.help_contents', PermissionManager.PERM_ACCESS),
        'HelpSupport': ('app.common.help_support', PermissionManager.PERM_ACCESS),
        'HelpHelp': ('app.common.help', PermissionManager.PERM_ACCESS),
    }

    def __init__(self, options, shell, store, app):
        """Creates a new window

        :param options: optparse options
        :param shell: the shell
        :param store: a store
        :param app: a Gtk.Application instance
        """
        self._action_groups = {}
        self._help_section = None
        self._osx_app = None
        self.current_app = None
        self.shell = shell
        self.app = app
        self.in_ui_test = False
        self.options = options
        self.store = store
        self._pre_launcher_init()
        Delegate.__init__(self, toplevel=Gtk.ApplicationWindow.new(app))
        self._create_ui()
        self._launcher_ui_bootstrap()

    def _pre_launcher_init(self):
        if platform.system() == 'Darwin':
            import gtk_osxapplication
            self._osx_app = gtk_osxapplication.OSXApplication()
            self._osx_app.connect(
                'NSApplicationBlockTermination',
                self._on_osx__block_termination)
            self._osx_app.set_use_quartz_accelerators(True)

        self._app_settings = api.user_settings.get('app-ui', {})
        self.main_vbox = Gtk.VBox()

    #
    # Private
    #

    def _create_application_actions(self):
        """Create the actions that activate the applications.

        This actions are prefixed by 'launch', followed by the app name (for
        instance launch.pos)
        """
        def callback(action, parameter, name):
            self.switch_application(name)

        group = Gio.SimpleActionGroup()
        self.toplevel.insert_action_group('launch', group)
        for app in self.get_available_applications():
            action = Gio.SimpleAction.new(app.name, None)
            action.connect('activate', callback, app.name)
            group.add_action(action)

        # Also add the launcher app
        action = Gio.SimpleAction.new('launcher', None)
        action.connect('activate', callback, 'launcher')
        group.add_action(action)

    def _create_menu2(self, actions):
        model = Gio.Menu()
        for action in actions or []:
            if isinstance(action, list):
                section = self._create_menu(action)
                model.append_section(None, section)
            else:
                label, name = action
                item = Gio.MenuItem.new(label, name)
                model.append_item(item)
        return model

    def _create_menu(self, actions):
        model = Gio.Menu()
        for action in actions or []:
            if isinstance(action, list):
                section = self._create_menu(action)
                model.append_section(None, section)
                pass
            else:
                fullname, icon, label, accel = self._action_specs[action.get_name()][:-1]
                item = Gio.MenuItem.new(label, fullname)
                if accel:
                    item.set_attribute_value('accel', GLib.Variant('s', accel))
                    self.app.set_accels_for_action(fullname, [accel])
                model.append_item(item)
        return model

    def _create_shared_ui(self):
        self.toplevel.add(self.main_vbox)

        self.application_box = Gtk.HBox()
        self.main_vbox.pack_start(self.application_box, True, True, 0)
        self.application_box.show()

        self.stoq_menu = PopoverMenu(self)
        self.main_vbox.show_all()

        self.statusbar = self._create_statusbar()
        self.statusbar.set_visible(True)

        self.main_vbox.pack_start(self.statusbar, False, False, 0)

        self.main_vbox.set_focus_chain([self.application_box])
        self._create_application_actions()

    def _create_statusbar(self):
        statusbar = ShellStatusbar(self)

        # Set the initial text, the currently logged in user and the actual
        # branch and station.
        user = api.get_current_user(self.store)
        station = api.get_current_station(self.store)
        status_str = '   |   '.join([
            _("User: %s") % (user.get_description(),),
            _("Computer: %s") % (station.name,),
            "PID: %s" % (os.getpid(),)
        ])
        statusbar.push(0, status_str)
        return statusbar

    def _osx_setup_menus(self):
        self.quit.set_visible(False)
        self.HelpAbout.set_visible(False)
        self.HelpAbout.set_label(_('About Stoq'))
        self._osx_app.set_help_menu(
            self.HelpMenu.get_proxies()[0])
        self._osx_app.insert_app_menu_item(
            self.HelpAbout.get_proxies()[0], 0)
        self._osx_app.insert_app_menu_item(
            Gtk.SeparatorMenuItem(), 1)
        self.preferences.set_visible(False)
        self._osx_app.insert_app_menu_item(
            self.Preferences.get_proxies()[0], 2)
        self._osx_app.ready()

    def _launcher_ui_bootstrap(self):
        self._restore_window_size()

        self.hide_app(empty=True)

        self._check_demo_mode()
        self._check_online_plugins()
        self._birthdays_bar = None
        self._check_client_birthdays()
        # json will restore tuples as lists. We need to convert them
        # to tuples or the comparison bellow won't work.
        actual_version = tuple(api.user_settings.get('actual-version', (0,)))
        if stoq.stoq_version > actual_version:
            api.user_settings.set('last-version-check', None)
            self._display_changelog_message()
            # Display the changelog message only once. Most users will never
            # click on the "See what's new" button, and that will affect its
            # visual identity.
            api.user_settings.set('actual-version', stoq.stoq_version)

        self._check_information()

        if not stoq.stable and not api.is_developer_mode():
            self._display_unstable_version_message()

        toplevel = self.get_toplevel()
        toplevel.connect('configure-event', self._on_toplevel__configure)
        toplevel.connect('delete-event', self._on_toplevel__delete_event)

        # A GtkWindowGroup controls grabs (blocking mouse/keyboard interaction),
        # by default all windows are added to the same window group.
        # We want to avoid setting modallity on other windows
        # when running a dialog using gtk_dialog_run/run_dialog.
        window_group = Gtk.WindowGroup()
        window_group.add_window(toplevel)

    def _check_online_plugins(self):
        # For each online plugin, try to download and install it.
        # Otherwise warn him
        online_plugins = InstalledPlugin.get_pre_plugin_names(self.store)
        if not online_plugins:
            return

        successes = []
        manager = get_plugin_manager()
        for plugin_name in online_plugins:
            success, msg = manager.download_plugin(plugin_name)
            successes.append(success)
            if success:
                manager.install_plugin(self.store, plugin_name)
                online_plugins.remove(plugin_name)

        if all(successes):
            return

        # Title
        title = _('You have pending plugins.')

        # Description
        url = 'https://stoq.link/?source=stoq-plugin-alert&amp;hash={}'.format(
            api.sysparam.get_string('USER_HASH'))
        desc = _(
            'The following plugins need to be enabled: <b>{}</b>.\n\n'
            'You can do it by registering on <a href="{}">Stoq.link</a>.'
        ).format(', '.join(online_plugins), url)
        msg = '<b>%s</b>\n%s' % (title, desc)
        self.add_info_bar(Gtk.MessageType.WARNING, msg)

    def _check_demo_mode(self):
        if not api.sysparam.get_bool('DEMO_MODE'):
            return

        if api.user_settings.get('hide-demo-warning'):
            return

        button_label = _('Use my own data')
        title = _('You are using Stoq with example data.')
        desc = (_("Some features are limited due to fiscal reasons. "
                  "Click on '%s' to remove the limitations.")
                % button_label)
        msg = '<b>%s</b>\n%s' % (api.escape(title), api.escape(desc))

        button = Gtk.Button(button_label)
        button.connect('clicked', self._on_enable_production__clicked)
        self.add_info_bar(Gtk.MessageType.WARNING, msg, action_widget=button)

    def _check_client_birthdays(self):
        if not api.sysparam.get_bool('BIRTHDAY_NOTIFICATION'):
            return

        # Display the info bar once per day
        date = api.user_settings.get('last-birthday-check')
        last_check = date and datetime.datetime.strptime(date, '%Y-%m-%d').date()
        if last_check and last_check >= datetime.date.today():
            return

        # Only display the infobar if the user has access to calendar (because
        # clicking on the button will open it) and to sales (because it
        # requires that permission to be able to check client details)
        user = api.get_current_user(self.store)
        if not all([user.profile.check_app_permission(u'calendar'),
                    user.profile.check_app_permission(u'sales')]):
            return

        branch = api.get_current_branch(self.store)
        clients_count = ClientWithSalesView.find_by_birth_date(
            self.store, datetime.datetime.today(), branch=branch).count()

        if clients_count:
            msg = stoqlib_ngettext(
                _("There is %s client doing birthday today!"),
                _("There are %s clients doing birthday today!"),
                clients_count) % (clients_count, )
            button = Gtk.Button(_("Check the calendar"))
            button.connect('clicked', self._on_check_calendar__clicked)

            self._birthdays_bar = self.add_info_bar(
                Gtk.MessageType.INFO,
                "<b>%s</b>" % (GLib.markup_escape_text(msg), ),
                action_widget=button)

    def _check_information(self):
        """Check some information with Stoq Web API

        - Check if there are new versions of Stoq Available
        - Check if this Stoq Instance uses Stoq Link (and send data to us if
          it does).
        """
        # Check version
        self._version_checker = VersionChecker(self.store, self)
        self._version_checker.check_new_version()

    def _display_changelog_message(self):
        msg = _("Welcome to Stoq version %s!") % stoq.version

        button = Gtk.Button(_("See what's new"))
        button.connect('clicked', self._on_show_changelog__clicked)

        self._changelog_bar = self.add_info_bar(Gtk.MessageType.INFO, msg,
                                                action_widget=button)

    def _display_unstable_version_message(self):
        msg = _(
            'You are currently using an <b>unstable version</b> of Stoq that '
            'is under development,\nbe aware that it may behave incorrectly, '
            'crash or even loose your data.\n<b>Do not use in production.</b>')
        self.add_info_bar(Gtk.MessageType.WARNING, msg)

    def _save_window_size(self):
        if not hasattr(self, '_width'):
            return
        # Do not save the size of the window when we are in fullscreen
        window = self.get_toplevel()
        window = window.get_window()
        if window.get_state() & Gdk.WindowState.FULLSCREEN:
            return
        d = api.user_settings.get('launcher-geometry', {})
        d['width'] = str(self._width)
        d['height'] = str(self._height)
        d['x'] = str(self._x)
        d['y'] = str(self._y)

    def _restore_window_size(self):
        d = api.user_settings.get('launcher-geometry', {})
        try:
            width = int(d.get('width', -1))
            height = int(d.get('height', -1))
            x = int(d.get('x', -1))
            y = int(d.get('y', -1))
        except ValueError:
            pass

        # Setup the default window size, for smaller sizes use
        # 75% of the height or 600px if it's higher than 800px
        screen = Gdk.Screen.get_default()
        screen_height = screen.get_height()
        screen_width = screen.get_width()

        if height == -1 or y > screen_height:
            height = min(int(screen_height * 0.75), 650)

        if width == -1 or y > screen_width:
            width = min(int(screen_width * 0.75), 800)

        # Setup window position according to the settings file, but if settings file
        # indicates values out of the screen, move the window to the outermost position
        # within the screen.
        toplevel = self.get_toplevel()
        toplevel.set_default_size(width, height)
        y = min(y, screen_height - height)
        x = min(x, screen_width - width)
        if x != -1 and y != -1:
            toplevel.move(x, y)

    def _read_resource(self, domain, name):
        from stoqlib.lib.kiwilibrary import library

        # On development, documentation resources (e.g. COPYING) will
        # be located directly on the library's root
        devpath = os.path.join(library.get_root(), name)
        if os.path.exists(devpath):
            with open(devpath) as f:
                return f.read()

        return environ.get_resource_string('stoq', domain, name).decode()

    def _run_about(self):
        info = get_utility(IAppInfo)
        about = Gtk.AboutDialog()
        about.set_name(info.get("name"))
        about.set_version(info.get("version"))
        about.set_website(stoq.website)
        release_date = stoq.release_date
        about.set_comments(_('Release date: %s') %
                           datetime.datetime(*release_date).strftime('%x'))
        about.set_copyright('Copyright (C) 2005-2012 Async Open Source')

        about.set_logo(render_logo_pixbuf('about'))

        # License

        if locale.getlocale()[0] == 'pt_BR':
            filename = 'COPYING.pt_BR'
        else:
            filename = 'COPYING'
        data = self._read_resource('docs', filename)
        about.set_license(data)

        # Authors & Contributors
        data = self._read_resource('docs', 'AUTHORS')
        lines = data.split('\n')
        lines.append('')  # separate authors from contributors
        data = self._read_resource('docs', 'CONTRIBUTORS')
        lines.extend([c.strip() for c in data.split('\n')])
        about.set_authors(lines)

        about.set_transient_for(get_current_toplevel())
        about.run()
        about.destroy()

    def _hide_current_application(self):
        if not self.current_app:
            return False

        if self._shutdown_application():
            self.hide_app()
        return True

    def _show_uri(self, uri):
        toplevel = self.get_toplevel()
        open_browser(uri, toplevel.get_screen())

    def _empty_message_area(self):
        area = self.statusbar.message_area
        for child in area.get_children()[1:]:
            child.destroy()

    def _shutdown_application(self, restart=False, force=False):
        """Shutdown the application:
        There are 3 possible outcomes of calling this function, depending
        on how many windows and applications are open::

        * Hide application, save state, show launcher
        * Close window, save state, show launcher
        * Close window, save global state, terminate application

        This function is called in the following situations:
        * When closing a window (delete-event)
        * When clicking File->Close in an application
        * When clicking File->Quit in the launcher
        * When clicking enable production mode (restart=True)
        * Pressing Ctrl-w/F5 in an application
        * Pressing Ctrl-q in the launcher
        * Pressing Alt-F4 on Win32
        * Pressing Cmd-q on Mac

        :returns: True if shutdown was successful, False if not
        """
        log.debug("Shutting down launcher")

        # Ask the application if we can close, currently this only happens
        # when trying to close the POS app if there's a sale in progress
        current_app = self.current_app
        if current_app and not current_app.can_close_application():
            return False

        # We can currently only close a window if the currently active
        # application is the launcher application, unless we force it
        # (e.g. when enabling production mode)
        if current_app and current_app.app_name != 'launcher' and not force:
            return True

        # Here we save app specific state such as object list
        # column position/ordering
        if current_app and current_app.search:
            current_app.search.save_columns()

        self._save_window_size()

        self.shell.close_window(self)

        # If there are other windows open, do not terminate the application, just
        # close the current window and leave the others alone
        if self.shell.windows:
            return True

        self.shell.quit(restart=restart)

    def _create_ui(self):
        if self._osx_app:
            self._osx_setup_menus()

        self._create_headerbar()
        self._create_actions()
        self._create_shared_ui()

        toplevel = self.get_toplevel().get_toplevel()
        add_current_toplevel(toplevel)

        if self.options.debug:
            self.add_debug_ui()

    def create_button(self, icon, label=None, menu_model=None, menu=False, action=None,
                      tooltip=None, style_class=None, toggle=False, icon_size=Gtk.IconSize.BUTTON):
        if menu_model or menu:
            button = Gtk.MenuButton()
        elif toggle:
            button = Gtk.ToggleButton()
        else:
            button = Gtk.Button()

        box = Gtk.HBox(spacing=6)
        button.add(box)

        if icon:
            image = Gtk.Image.new_from_icon_name(icon, icon_size)
            box.pack_start(image, False, False, 0)
        if label:
            label = Gtk.Label.new(label)
            box.pack_start(label, False, False, 0)

        if menu_model:
            button.set_menu_model(menu_model)
        if action:
            button.set_action_name(action)
        if tooltip:
            button.set_tooltip_text(tooltip)
        if style_class:
            button.get_style_context().add_class(style_class)
        return button

    def _create_headerbar(self):
        # User/help menu
        user = api.get_current_user(self.store)
        xml = MENU_XML.format(username=api.escape(user.get_description()),
                              preferences=_('Preferences...'), password=_('Change password...'),
                              signout=_('Sign out...'), help=_('Help'),
                              contents=_('Contents'), translate=_('Translate Stoq...'),
                              get_support=_('Get support online...'), chat=_('Online chat...'),
                              about=_('About'), quit=_('Quit'))
        builder = Gtk.Builder.new_from_string(xml, -1)

        # Header bar
        self.header_bar = Gtk.HeaderBar()
        self.toplevel.set_titlebar(self.header_bar)

        # Right side
        self.close_btn = self.create_button('fa-power-off-symbolic', action='stoq.quit')
        self.close_btn.set_relief(Gtk.ReliefStyle.NONE)
        self.min_btn = self.create_button('fa-window-minimize-symbolic')
        self.min_btn.set_relief(Gtk.ReliefStyle.NONE)
        #self.header_bar.pack_end(self.close_btn)
        #self.header_bar.pack_end(self.min_btn)
        box = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        box.pack_start(self.min_btn, False, False, 0)
        box.pack_start(self.close_btn, False, False, 0)
        self.header_bar.pack_end(box)

        self.user_menu = builder.get_object('app-menu')
        self.help_section = builder.get_object('help-section')
        self.user_button = self.create_button('fa-cog-symbolic',
                                              menu_model=self.user_menu)
        self.search_menu = Gio.Menu()
        self.search_button = self.create_button('fa-search-symbolic', _('Searches'),
                                                menu_model=self.search_menu)
        self.main_menu = Gio.Menu()
        self.menu_button = self.create_button('fa-bars-symbolic', _('Actions'),
                                              menu_model=self.main_menu)

        self.header_bar.pack_end(
            ButtonGroup([self.menu_button, self.search_button, self.user_button]))

        self.sign_button = self.create_button('', _('Sign now'), style_class='suggested-action')
        #self.header_bar.pack_end(self.sign_button)

        # Left side
        self.home_button = self.create_button(STOQ_LAUNCHER, style_class='suggested-action')
        self.new_menu = Gio.Menu()
        self.new_button = self.create_button('fa-plus-symbolic', _('New'),
                                             menu_model=self.new_menu)

        self.header_bar.pack_start(
            ButtonGroup([self.home_button, self.new_button, ]))

        self.domain_header = None
        self.header_bar.show_all()

        self.notifications = NotificationCounter(self.home_button, blink=True)

    def _create_actions(self):
        # Gloabl actions avaiable at any time from all applications
        actions = [
            ('preferences', None),
            ('export', None),
            ('print', None),
            ('sign_out', None),
            ('change_password', None),
            ('HelpApp', None),
            ('HelpContents', None),
            ('HelpTranslate', None),
            ('HelpSupport', None),
            ('HelpChat', None),
            ('HelpAbout', None),
            ('quit', None),
        ]

        pm = PermissionManager.get_permission_manager()
        group = Gio.SimpleActionGroup()
        self.toplevel.insert_action_group('stoq', group)
        for (name, param_type) in actions:
            action = Gio.SimpleAction.new(name, param_type)
            group.add_action(action)
            # Save the action in self so that auto signal connections work
            setattr(self, name, action)

            # Check permissions
            key, required = self.action_permissions.get(name,
                                                        (None, pm.PERM_ALL))
            if not pm.get(key) & required:
                action.set_enabled(False)

    def _load_shell_app(self, app_name):
        user = api.get_current_user(self.store)

        # FIXME: Move over to domain
        if (app_name != 'launcher' and
                not user.profile.check_app_permission(app_name)):
            error(_("This user lacks credentials \nfor application %s") %
                  app_name)
            return None
        module = __import__("stoq.gui.%s" % (app_name, ),
                            globals(), locals(), [''])
        attribute = app_name.capitalize() + 'App'
        shell_app_class = getattr(module, attribute, None)
        if shell_app_class is None:
            raise SystemExit("%s app misses a %r attribute" % (
                app_name, attribute))

        shell_app_class.app_name = app_name
        shell_app = shell_app_class(window=self,
                                    store=self.store)

        return shell_app

    def _wrap_action_callback(self, app, name):
        """Wraps a Gtk.Action callback to a Gio.SimpleAction callback.

        This is just a temporary wrapper untill all apps are properly migrated
        to the new gtk api.
        """
        method_name = 'on_%s__activate' % name
        try:
            old_callback = getattr(app, method_name)
        except AttributeError:
            return

        def new_callback(action, parameter):
            return old_callback(action)

        setattr(app, method_name, new_callback)

    #
    # Public API
    #

    def add_ui_actions(self, app, actions, name):
        for spec in actions:
            spec = list(spec)
            act_name, icon, label = spec[:3]
            accel, long_desc, callback = None, None, None
            spec[:3] = []
            if spec:
                accel = spec.pop(0)
            if spec:
                long_desc = spec.pop(0)
            if spec:
                callback = spec.pop(0)

            param_type = None
            if name == 'Actions':
                action = Gio.SimpleAction.new(act_name, param_type)
                self._wrap_action_callback(app, act_name)
            elif name in ('ToggleActions', 'RadioActions'):
                action = Gio.SimpleAction.new_stateful(act_name, param_type,
                                                       GLib.Variant.new_boolean(False))

                def set_active(value):
                    # TODO: colocar deprecation warning aqui.
                    action.set_state(GLib.Variant.new_boolean(value))
                    # XXX: The change-state event is not being emitted
                    method_name = 'on_%s__activate' % act_name
                    getattr(app, method_name)(action, None)

                def get_active():
                    value = action.get_state().get_boolean()
                    return value

                action.set_active = set_active
                action.get_active = get_active

            app.action_group.add_action(action)
            app.window._action_specs[act_name] = (
                app.app_name + '.' + act_name, icon, label, accel, long_desc)
            # Save the action in the app so that auto signal connections work
            setattr(app, act_name, action)

            if callback:
                action.connect('activate', callback)

    def set_close_button_icon(self, icon_name):
        image = self.close_btn.get_child().get_children()[0]
        image.set_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
        image.set_size_request(16, 16)

    def show_app(self, app, app_window, **params):
        self.stoq_menu.set_visible(False)
        app_window.get_parent().remove(app_window)
        self.application_box.add(app_window)
        self.application_box.set_child_packing(app_window, True, True, 0,
                                               Gtk.PackType.START)

        self._current_app_settings = self._app_settings.setdefault(app.app_name, {})

        self.header_bar.set_title(app.get_title())
        self.header_bar.set_subtitle(app.app_title)
        self.application_box.show()
        app.toplevel = self.get_toplevel()

        if self._birthdays_bar is not None:
            if app.app_name in ['launcher', 'sales']:
                self._birthdays_bar.show()
            else:
                self._birthdays_bar.hide()

        if app.app_name == 'launcher':
            icon_name = 'fa-power-off-symbolic'
        else:
            icon_name = 'fa-chevron-left-symbolic'
        self.set_close_button_icon(icon_name)

        # StartApplicationEvent must be emitted before calling app.activate(),
        # so that the plugins can have the chance to modify the application
        # before any other event is emitted.
        StartApplicationEvent.emit(app.app_name, app)
        app.activate(**params)

        self.current_app = app
        self.current_widget = app_window

        if not self.in_ui_test:
            while Gtk.events_pending():
                Gtk.main_iteration()
            app_window.show()
        app.setup_focus()

    def hide_app(self, empty=False):
        """
        Hide the current application in this window

        :param bool empty: if ``True``, do not add the default launcher application
        """
        self.application_box.hide()
        # Reset menus/headerbar
        self.main_menu.remove_all()
        self.search_menu.remove_all()
        self.new_menu.remove_all()

        if self.current_app:
            inventory_bar = getattr(self.current_app, 'inventory_bar', None)
            if inventory_bar:
                inventory_bar.hide()
            if self.current_app.search:
                self.current_app.search.save_columns()
            self.current_app.deactivate()

            # We need to remove the accels for this app, otherwise they would
            # still be active from other applications
            for spec in self._action_specs.values():
                fullname, icon, label, accel = spec[:-1]
                if accel:
                    self.app.set_accels_for_action(fullname, [])

            if self._help_section:
                self.help_section.remove(0)
                self._help_section = None
            self.current_widget.destroy()

            StopApplicationEvent.emit(self.current_app.app_name,
                                      self.current_app)
            self.current_app = None

        self._empty_message_area()
        if not empty:
            self.run_application(app_name=u'launcher')

    def add_info_bar(self, message_type, label, action_widget=None):
        """Show an information bar to the user.

        :param message_type: message type, a Gtk.MessageType
        :param label: label to display
        :param action_widget: optional, most likely a button
        :returns: the infobar
        """
        infobar = MessageBar(label, message_type)

        if action_widget:
            infobar.add_action_widget(action_widget, 0)
            action_widget.show()
        infobar.show()

        self.main_vbox.pack_start(infobar, False, False, 0)
        self.main_vbox.reorder_child(infobar, 0)
        return infobar

    def set_help_section(self, label, section):
        self._help_section = section
        self.help_section.insert(0, label, 'stoq.HelpApp')

    def add_debug_ui(self):
        self.toplevel.set_interactive_debugging(True)
        return
        actions = [
            ('Introspect', None, _('Introspect slaves')),
            ('RemoveSettingsCache', None, _('Remove settings cache')),
        ]

        self.add_ui_actions(self, actions, 'Actions')
        self.add_extra_items([self.Introspect, self.RemoveSettingsCache],
                             _('Debug'))

    def add_domain_header(self, options):
        if self.domain_header:
            self.header_bar.remove(self.domain_header)
            self.domain_header = None

        if not options:
            return

        buttons = []
        for (icon, label, action, in_header) in options:
            if not in_header:
                continue
            buttons.append(self.create_button(icon, action=action,
                                              tooltip=label))
        self.domain_header = ButtonGroup(buttons)
        self.domain_header.show_all()
        self.header_bar.pack_start(self.domain_header)

    def add_new_items(self, actions, label=None):
        self.new_menu.append_section(label, self._create_menu(actions))

    def add_new_items2(self, actions, label=None):
        self.new_menu.append_section(label, self._create_menu2(actions))

    def add_export_items(self, actions=None):
        self._export_menu = self._create_menu(actions)
        self._export_menu.insert(0, _('Export to spreadsheet...'), 'stoq.export')
        self.main_menu.append_section(None, self._export_menu)

    def add_print_items(self, actions=None):
        self._print_menu = self._create_menu(actions)
        self._print_menu.insert(0, _('Print this report...'), 'stoq.print')
        self.main_menu.append_section(None, self._print_menu)

    def add_print_items2(self, actions=None):
        self._print_menu = self._create_menu2(actions)
        self._print_menu.insert(0, _('Print this report...'), 'stoq.print')
        self.main_menu.append_section(None, self._print_menu)

    def add_extra_items(self, actions=None, label=None):
        self._extra_items = self._create_menu(actions)
        self.main_menu.append_section(label, self._extra_items)

    def add_extra_items2(self, actions=None, label=None):
        self._extra_items = self._create_menu2(actions)
        self.main_menu.append_section(label, self._extra_items)

    def add_search_items(self, actions, label=None):
        self.search_menu.append_section(label, self._create_menu(actions))

    def close(self):
        """
        Closes this window
        """
        self.hide_app(empty=True)
        self.toplevel.destroy()
        self.hide()

    def switch_application(self, app_name, **params):
        params['hide'] = True
        self.run_application(app_name, **params)

    def run_application(self, app_name, **params):
        """
        Load and show an application in a shell window.

        :param ShellWindow shell_window: shell window to run application in
        :param str appname: the name of the application to run
        :param params: extra arguments passed to the application
        :returns: the shell application or ``None`` if the user doesn't have
          access to open the application
        :rtype: ShellApp
        """
        # FIXME: Maybe we should really have an app that would be responsible
        # for doing administration tasks related to stoqlink here? Right now
        # we are only going to open the stoq.link url
        if app_name == 'link':
            toplevel = self.get_toplevel()
            user_hash = api.sysparam.get_string('USER_HASH')
            url = 'https://stoq.link?source=stoq&hash={}'.format(user_hash)
            open_browser(url, toplevel.get_screen())
            return

        if params.pop('hide', False):
            self.hide_app(empty=True)

        shell_app = self._load_shell_app(app_name)
        if shell_app is None:
            return None

        # Set the icon for the application
        app_icon = get_application_icon(app_name)
        toplevel = self.get_toplevel()
        toplevel.set_icon_name(app_icon)

        # FIXME: We should remove the toplevel windows of all ShellApp's
        #        glade files, as we don't use them any longer.
        shell_app_window = shell_app.get_toplevel()
        self.show_app(shell_app, shell_app_window.get_child(), **params)
        shell_app_window.hide()

        return shell_app

    def get_available_applications(self):
        user = api.get_current_user(self.store)

        permissions = user.profile.get_permissions()
        descriptions = get_utility(IApplicationDescriptions).get_descriptions()

        available_applications = []

        # sorting by app_full_name
        for name, full, icon, descr in locale_sorted(
                descriptions, key=operator.itemgetter(1)):
            if permissions.get(name):
                available_applications.append(
                    Application(name, full, icon, descr))
        return available_applications

    #
    # Kiwi callbacks
    #

    def key_F5(self):
        self.switch_application('launcher')
        return True

    def _on_osx__block_termination(self, app):
        return not self._shutdown_application()

    def _on_show_changelog__clicked(self, button):
        show_section('changelog')
        self._changelog_bar.hide()

    def _on_check_calendar__clicked(self, button):
        self.switch_application(u'calendar')
        api.user_settings.set('last-birthday-check',
                              datetime.date.today().strftime('%Y-%m-%d'))
        self._birthdays_bar.hide()
        self._birthdays_bar = None

    def _on_toplevel__configure(self, widget, event):
        window = widget.get_window()
        rect = window.get_frame_extents()
        self._x = rect.x
        self._y = rect.y
        self._width = event.width
        self._height = event.height

    def _on_toplevel__delete_event(self, *args):
        if self._hide_current_application():
            return True

        self._shutdown_application()

    def _on_enable_production__clicked(self, button):
        if not self.current_app.can_close_application():
            return
        if not yesno(_(u"This will enable production mode and finish the "
                       u"demonstration. Are you sure?"),
                     Gtk.ResponseType.NO,
                     _(u"Enable production mode"), _(u"Continue testing")):
            return

        api.config.set('Database', 'enable_production', 'True')
        api.config.flush()
        self._shutdown_application(restart=True, force=True)

    def on_min_btn__clicked(self, button):
        self.get_toplevel().iconify()

    # File

    def on_SearchToolItem__activate(self, action):
        if self.current_app:
            self.current_app.search_activate()

    def on_print__activate(self, action, parameter):
        self.current_app.print_activate()

    def on_export__activate(self, action, parameter):
        self.current_app.export_spreadsheet_activate()

    def on_change_password__activate(self, action, parameter):
        from stoqlib.gui.slaves.userslave import PasswordEditor
        store = api.new_store()
        user = api.get_current_user(store)
        retval = run_dialog(PasswordEditor, self, store, user)
        store.confirm(retval)
        store.close()

    def on_sign_out__activate(self, action, parameter):
        from stoqlib.lib.interfaces import ICookieFile
        get_utility(ICookieFile).clear()
        self._shutdown_application(restart=True)

    def on_quit__activate(self, action, parameter):
        if self._hide_current_application():
            return

        self._shutdown_application()
        self.get_toplevel().destroy()

    def on_home_button__clicked(self, action):
        self.stoq_menu.toggle()

    # View

    def on_preferences__activate(self, action, parameter):
        with api.new_store() as store:
            run_dialog(PreferencesEditor, self, store)

    # Help

    def on_HelpApp__activate(self, action, parameter):
        show_section(self._help_section)

    def on_HelpContents__activate(self, action, parameter):
        show_contents()

    def on_HelpTranslate__activate(self, action, parameter):
        self._show_uri("https://www.transifex.com/projects/p/stoq")

    def on_HelpChat__activate(self, action, parameter):
        self._show_uri("http://www.stoq.com.br/")

    def on_HelpSupport__activate(self, action, parameter):
        self._show_uri("http://www.stoq.com.br/suporte")

    def on_HelpAbout__activate(self, action, parameter):
        self._run_about()

    def on_main_menu__items_changed(self, menu, position, removed, added):
        self.menu_button.set_sensitive(menu.get_n_items() > 0)

    def on_search_menu__items_changed(self, menu, position, removed, added):
        self.search_button.set_sensitive(menu.get_n_items() > 0)

    def on_new_menu__items_changed(self, menu, position, removed, added):
        self.new_button.set_sensitive(menu.get_n_items() > 0)

    # Debug

    def on_Introspect__activate(self, action):
        window = self.get_toplevel()
        introspect_slaves(window)

    def on_RemoveSettingsCache__activate(self, action):
        keys = ['app-ui', 'launcher-geometry']
        keys.append('search-columns-%s' % (
            api.get_current_user(api.get_default_store()).username, ))

        for key in keys:
            try:
                api.user_settings.remove(key)
            except KeyError:
                pass