New Calculator Applet

Guys, I made a source code for a PyGtk Applet frame. Can anyone help me with putting it up in a popover for a panel ?

The source code for the calculator frame which is made is this.

Can anyone help me with theming this ? ( I just need to theme the equal button )
:slight_smile:

This might not be the answer you are looking for, but I stripped back the Budgie Countdown applet to the bare basics of showing a popover, where you should be able to add your own widgets and code. It might help you translate your app to a budgie popover. I have it posted here (the part you want to pay attention to is the maingrid):
https://github.com/samlane-ma/budgie-empty-popover

I strongly recommend this link:

@fossfreedom and @vlijm have taught me more than they may realize from their applets alone…

3 Likes

So, I tried doing this. But it isn’t working. :sweat_smile:
This is the repository.

What I would do is add all your grid entries from the calculator app directly to the main popover grid. In my example, you should be able to change the “maingrid” to “grid” on these lines, then copy/paste a lot of your code into it.

self.maingrid = Gtk.Grid()
self.popover.add(self.maingrid)

""" maingrid is where to attach all the functions for the budgie popup
"""
self.maingrid.show_all()

I can explain better later on and give a better example if you need, only have access to a phone right now…

1 Like

As a general remark, I’d be restraint to add complete applications in a popover as an applet. Remember everything in the panel runs from one and the same thread. The panel then is as stable as its least stable applet, and anything that goes wrong in an applet has a direct effect on the panel.
The savest policy then is to use the/an applet to control an application, not run it inside the applet.

2 Likes

Makes sense, thanks. Learning new things every day.

1 Like

I posted calculator.py / calculator.plugin to show you what I meant. I am not saying that this is the best way to do this, or even a good way. It might even be a downright horrible way. I’m just saying that it is a way which technically works. My knowledge of python is just over 3 weeks old, so I am guessing you have significantly more experience than me. I am merely doing this the only way I know how without much more research on my part. But it is your code essentially copy/pasted into the popover box from my example. (minus the css stuff which I know nothing about yet)

If it helps point you in the right direction, great! If not, I won’t be offended, as ultimately I am trying to learn from scratch so this is all good practice for me.

Edit: Also, I changed them to have your author/copyright info. Probably isn’t the best practice for me to post your code publicly without this info…

2 Likes

+1, That’s how it works :slight_smile:

To illustrate: try running from a terminal

budgie-panel --replace &

keep the terminal open, then entering incorrect input in the calculator:

1 + monkey

…and press =. In terminal, you’ll see the output:

Traceback (most recent call last):
  File "/home/jacob/.local/share/budgie-desktop/plugins/Calc/calculator.py", line 173, in go
    exec(f'self.entry.set_text(str({_}))')
  File "<string>", line 1, in <module>
NameError: name 'monkey' is not defined

…Which is then on the account of the panel.

Easily fixed in this case by changing the executing function:

def go(self, widget):
    _ = self.formatted_text()
    try:
        exec(f'self.entry.set_text(str({_}))')
    except Exception:
        pass
1 Like

Can’t resist suggest a tiny code reduction. In the code, there is a lot of repeating the same functionality and procedures. As an example, In the snippet below, The buttons 1-9 are created, connected and attached in one action. Those are easy, because in one range subsequently.
Here the code is reduced from 36 (real lines, now commented out) lines into 11 lines.

With other buttons, you can do it similarly, using col/row as arguments.

For example:

import gi.repository
gi.require_version('Budgie', '1.0')
from gi.repository import Budgie, GObject, Gtk, Gio
import os


"""
Budgie EmptyPopover

Author: Heavily Modified from CountDown applet by Jacob Vlijm
Copyright © 2017-2020 Ubuntu Budgie Developers
Website=https://ubuntubudgie.org
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or any later version. This
program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details. You
should have received a copy of the GNU General Public License along with this
program.  If not, see <http://www.gnu.org/licenses/>.
"""

class Calculator(GObject.GObject, Budgie.Plugin):
    """ This is simply an entry point into your Budgie Applet implementation.
        Note you must always override Object, and implement Plugin.
    """

    # Good manners, make sure we have unique name in GObject type system
    __gtype_name__ = "Calculator"

    def __init__(self):
        """ Initialisation is important.
        """
        GObject.Object.__init__(self)

    def do_get_panel_widget(self, uuid):
        """ This is where the real fun happens. Return a new Budgie.Applet
            instance with the given UUID. The UUID is determined by the
            BudgiePanelManager, and is used for lifetime tracking.
        """
        return CalculatorApplet(uuid)


class CalculatorApplet(Budgie.Applet):
    """ Budgie.Applet is in fact a Gtk.Bin """

    def __init__(self, uuid):
    
        box_padding = 3
        grid_padding = 3

        self.tab_message = ""
        Budgie.Applet.__init__(self)
        self.uuid = uuid

        # applet appearance
        self.icon = Gtk.Image()
        self.icon.set_from_icon_name(
            "gnome-calculator", Gtk.IconSize.MENU
        )
        self.panel_box = Gtk.EventBox()
        self.panel_box.add(self.icon)
        self.add(self.panel_box)
        self.popover = Budgie.Popover.new(self.panel_box)
        
        self.grid      = Gtk.Grid     ()
        self.sep       = Gtk.Separator()
        self.entry     = Gtk.Entry    ()
        self.entry_box = Gtk.Box      ()
        self.box       = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        self.box.pack_start(self.entry_box, True , True , box_padding)
        self.box.pack_start(self.sep      , True , True , 0        )
        self.box.pack_end  (self.grid     , True, True, box_padding)
        self.entry_box.pack_start(self.entry, True, True, box_padding)

        self.grid.set_row_spacing(grid_padding)
        self.grid.set_column_spacing(grid_padding)

        #########################################
        row = 1; col = 1
        for n in range(1, 10):
            if (n-1)%3 == 0:
                row = row + 1
                col = 1
            self.newbutton = Gtk.Button.new_with_label(str(n))
            self.newbutton.connect("clicked", self.fnc_button, str(n))
            self.grid.attach(self.newbutton, col, row, 1, 1)
            col = col + 1
        ##########################################

        # This is where all the buttons are defined.
        self.buttonC      = Gtk.Button.new_with_label('C')
##        self.button1      = Gtk.Button.new_with_label('1')
##        self.button2      = Gtk.Button.new_with_label('2')
##        self.button3      = Gtk.Button.new_with_label('3')
##        self.button4      = Gtk.Button.new_with_label('4')
##        self.button5      = Gtk.Button.new_with_label('5')
##        self.button6      = Gtk.Button.new_with_label('6')
##        self.button7      = Gtk.Button.new_with_label('7')
##        self.button8      = Gtk.Button.new_with_label('8')
##        self.button9      = Gtk.Button.new_with_label('9')
        self.button0      = Gtk.Button.new_with_label('0')
        self.buttonbpar   = Gtk.Button.new_with_label('(')
        self.buttonfpar   = Gtk.Button.new_with_label(')')
        self.buttonplus   = Gtk.Button.new_with_label('+')
        self.buttonequal  = Gtk.Button.new_with_label('=')
        self.buttonminus  = Gtk.Button.new_with_label('-')
        self.buttondivide = Gtk.Button.new_with_label('÷')
        self.buttonmulti  = Gtk.Button.new_with_label('×')
        self.buttondot    = Gtk.Button.new_with_label('.')
        self.buttonback   = Gtk.Button.new_with_label('🠄')
        self.buttonroot   = Gtk.Button.new_with_label('')
        self.buttonpower  = Gtk.Button.new_with_label('^')

##        self.button1.connect("clicked",self.fnc_button1)
##        self.button2.connect("clicked",self.fnc_button2)
##        self.button3.connect("clicked",self.fnc_button3)
##        self.button4.connect("clicked",self.fnc_button4)
##        self.button5.connect("clicked",self.fnc_button5)
##        self.button6.connect("clicked",self.fnc_button6)
##        self.button7.connect("clicked",self.fnc_button7)
##        self.button8.connect("clicked",self.fnc_button8)
##        self.button9.connect("clicked",self.fnc_button9)
        self.button0.connect("clicked",self.fnc_button0)
        self.buttonplus.connect("clicked",self.fnc_buttonplus)
        self.buttonfpar.connect("clicked",self.fnc_buttonfpar)
        self.buttonbpar.connect("clicked",self.fnc_buttonbpar)
        self.buttonminus.connect("clicked",self.fnc_buttonminus)
        self.buttondivide.connect("clicked",self.fnc_buttondivide)
        self.buttonmulti.connect("clicked",self.fnc_buttonmulti)
        self.buttondot.connect("clicked",self.fnc_buttondot)
        self.buttonpower.connect('clicked',self.fnc_buttonpower)
        

        self.buttonequal.connect('clicked', self.go            )
        self.buttonback.connect ('clicked', self.backspace_func)
        self.buttonC.connect    ('clicked', self.clear         )

        self.grid.set_border_width(3)
        
        
        # Here all the buttons are positioned.
        self.grid.attach(self.buttonC     , 1, 1, 1, 1)
        self.grid.attach(self.buttonbpar  , 2, 1, 1, 1)
        self.grid.attach(self.buttonfpar  , 3, 1, 1, 1)
        self.grid.attach(self.buttonback  , 4, 1, 1, 1)
##        self.grid.attach(self.button1     , 1, 2, 1, 1)
##        self.grid.attach(self.button2     , 2, 2, 1, 1)
##        self.grid.attach(self.button3     , 3, 2, 1, 1)
        self.grid.attach(self.buttonplus  , 4, 2, 1, 1)
##        self.grid.attach(self.button4     , 1, 3, 1, 1)
##        self.grid.attach(self.button5     , 2, 3, 1, 1)
##        self.grid.attach(self.button6     , 3, 3, 1, 1)
        self.grid.attach(self.buttonmulti , 4, 3, 1, 1)
        self.grid.attach(self.buttonback  , 5, 3, 1, 1)
##        self.grid.attach(self.button7     , 1, 4, 1, 1)
##        self.grid.attach(self.button8     , 2, 4, 1, 1)
##        self.grid.attach(self.button9     , 3, 4, 1, 1)
        self.grid.attach(self.buttonminus , 4, 4, 1, 1)
        self.grid.attach(self.buttondot   , 1, 5, 1, 1)
        self.grid.attach(self.button0     , 2, 5, 1, 1)
        self.grid.attach(self.buttonpower , 3, 5, 1, 1)
        self.grid.attach(self.buttondivide, 4, 5, 1, 1)
        
        self.entry_box.pack_end(self.buttonequal, False, False, box_padding)
        self.popover.add(self.box)
        self.box.show_all()
        self.grid.show_all()
        self.panel_box.show_all()
        self.show_all()
        self.panel_box.connect("button-press-event", self.on_press)

#############################################################
    def fnc_button(self, button, figure):
        self.entry.set_text(self.entry.get_text() + figure)
##############################################################
        

    def go(self, widget):
        _ = self.formatted_text()
        exec(f'self.entry.set_text(str({_}))')
    
    def backspace_func(self, widget):
        self.entry.set_text(self.entry.get_text()[:-1])
        
    def clear(self, button):
        self.entry.set_text('')
        
    def formatted_text(self):
        return self.entry.get_text().replace(' ','').replace('[','(').replace(']',')')\
            .replace('×','*').replace('^','**').replace('÷','/')
                   
   # Now here are the functions for normal buttons
   
##    def fnc_button1(self, button): self.entry.set_text(self.entry.get_text() + '1') 
##
##    def fnc_button2(self, button): self.entry.set_text(self.entry.get_text() + '2') 
##       
##    def fnc_button3(self, button): self.entry.set_text(self.entry.get_text() + '3') 
##     
##    def fnc_button4(self, button): self.entry.set_text(self.entry.get_text() + '4') 
##        
##    def fnc_button5(self, button): self.entry.set_text(self.entry.get_text() + '5') 
##         
##    def fnc_button6(self, button): self.entry.set_text(self.entry.get_text() + '6') 
##         
##    def fnc_button7(self, button): self.entry.set_text(self.entry.get_text() + '7')      
##    
##    def fnc_button8(self, button): self.entry.set_text(self.entry.get_text() + '8') 
##     
##    def fnc_button9(self, button): self.entry.set_text(self.entry.get_text() + '9') 
     
    def fnc_button0(self, button): self.entry.set_text(self.entry.get_text() + '0') 
         
    def fnc_buttonplus(self, button): self.entry.set_text(self.entry.get_text() + '+') 
    
    def fnc_buttonfpar(self, button): self.entry.set_text(self.entry.get_text() + ')') 
         
    def fnc_buttonbpar(self, button): self.entry.set_text(self.entry.get_text() + '(')      
    
    def fnc_buttonminus(self, button): self.entry.set_text(self.entry.get_text() + '-')      
    
    def fnc_buttondivide(self, button): self.entry.set_text(self.entry.get_text() + '÷')      
    
    def fnc_buttonmulti(self, button): self.entry.set_text(self.entry.get_text() + '×')      
    
    def fnc_buttondot(self, button): self.entry.set_text(self.entry.get_text() + '.')      
    
    def fnc_buttonrem(self, button): self.entry.set_text(self.entry.get_text() + '%')
    
    def fnc_buttonpower(self, button): self.entry.set_text(self.entry.get_text() + '^') 


    def on_press(self, panel_box, arg):
        self.manager.show_popover(self.panel_box)

    def do_update_popovers(self, manager):
        self.manager = manager
        self.manager.register_popover(self.panel_box, self.popover)

    def do_supports_settings(self):
        """Return True if support setting through Budgie Setting,
        False otherwise.
        """
        return False

Ah… good good!

Note. All of the gui setup could also be stripped out by using glade to create a .ui file and loading that.

Lol. In fact with python could have just one method with a param for all calc functions and a case statement to do the calculations.

2 Likes

At the risk of taking this a bit off-topic, any recommendations of budgie applets using the .ui files to look at? If not, I’m sure I can find plenty examples out there.

I actually designed it using Glade, but then i thought that it will slow down a bit. So I remade it in pure Python.