캐노니컬에서 안드로이드 폰을 위한 우분투를 발표 했습니다. 평소에는 스마트폰으로 사용하다가 집에서는 데스크톱 대용으로 사용하는 일이 앞으로는 일상다반사가 될지도 모르겠습니다. 모토로라의 아트릭스가 비슷한 컨셉으로 출시를 했지만 데스크톱의 온전한 기능을 갖추고 있지는 못했기 때문에 큰 반향을 불러일으키지는 못했습니다. 더군다나 전용 독에서만 동작하기 때문에 주머니를 털어서라도 구매할 만큼의 뽐뿌는 일으키지는 못했죠.

하지만 이번 캐노니컬의 발표는 많은 유저층을 갖고 있는 우분투 데스크톱의 환경을 그대로 재연했다는 것과, 추가적인 하드웨어가 필요 없다는 것(최소한의 하드웨어 스펙을 요구하긴 하지만), 우분투에서 안드로이드의 서비스와 데이터를 사용할 수 있다는 점, 듀얼 부팅이 아닌 것이 흥미로운 부분입니다. 내부적으로 Convergence API 모듈을 사용해서 데스크톱과 모바일 환경을 아우르기 때문에 우분투로 전환 된 상태에서도 문자 메시지를 읽거나 전화를 받는 것이 가능하다고 합니다. 그리고 구글 크로미움 브라우저, 구글 달력, 구글 문서, 썬더버드, VLC 플레이어, SNS 클라이언트, 뮤직 플레이어 등이 기본적으로 탑재되어있기 때문에 당장 세컨드 PC로 활용을 해도 부족함이 없어보입니다. 

이번 발표가 내 손안의 PC가 이제 내 방으로까지 영역을 팽창시키는 사건이 되지 않을까 기대해봅니다. 모바일 OS와의 정면 도전이 아닌 완전히 새로운 길을 만들어나가는 캐노니컬의 재치있는 행보도 재미있는 것 같습니다. 신의 한수가 될지는 조금 더 지켜봐야 되겠지만 말이죠. :)

참고 

저작자 표시 비영리 변경 금지
신고
Posted by 완소타코

댓글을 달아 주세요

  1. 마빈박사 2012.02.22 21:59 신고  댓글주소  수정/삭제  댓글쓰기

    무선과 VNC같은 툴을 활용하면
    꼭 Dock이 없어도 쉽게 사용할 수 있을 법 한데요.
    영상재생에 무리가 있을지언정 일반 사용은 문제가 없으니까요. ^^;

  2. 2012.02.28 12:32 신고  댓글주소  수정/삭제  댓글쓰기

    이건 어플 이름이 낚시같네요^^;;
    Ubuntu for Android 이름만보면
    누구나 안드로이드폰에서 우분투를 작동시키는걸로 착각하겠네요;;

얼마 전 Qt 커머셜 블로그에 '삭제할 것인가 말 것인가'라는 제목으로 객체 소멸에 대한 글이 포스팅 된 적이 있습니다. C/C++에서 동적 메모리는 사용 후 메모리를 직접 해제해야 하지만 Qt에서는 QObject를 상속받은 클래스에 대해 동적으로 생성된 메모리(객체)를 자동으로 삭제해줍니다. Qt 프레임워크 대부분의 클래스가 QObject를 상속받기 때문에 사실상 메모리 해제에 크게 신경 쓰지 않아도 됩니다.

원문 포스팅의 내용은 이벤트 핸들러나 슬롯 내부에서 delete시 문제점과 그 대안으로 이벤트 핸들러나 슬롯이 종료된 후 객체를 소멸하는 deleteLater() 함수에 대해 설명하고 있습니다. 이 현상은 저도 겪어본 적이 있는데 특정 시그널과 연결된 슬롯에서 이벤트와 시그널 관련된 객체를 삭제해야 한다면 deleteLater()를 잘 활용할 필요가 있습니다.

위 내용과 별개로 최근에 Qt를 사용하면서 객체 소멸과 관련된 일이 떠올라서 포스팅을 남겨봅니다. Qt 프레임워크의 객체 자동 소멸 메커니즘은 스마트하지만 저처럼 메모리 해제에 유달리 강박증이 있는 사람은 이 기능으로 인해 딜레마에 빠지게 됩니다.

"직접 해제할 것인가? 아니면 프레임워크에 맡길 것인가?" 프레임워크의 기능을 못 믿는 것은 아니지만 텅 빈 소멸자를 보고 있노라면 왠지 볼일 보고 뒤를 닦지 않은 것처럼 마음이 불편해서 견딜 수가 없습니다. 그래서 저는 소멸자에 일일이 동적 메모리 해제 코드를 삽입합니다. 그런데 이러한 꼼꼼함이 간혹 문제로 이어지는 상황이 있습니다. QObject는 내부적으로 자식 객체를 관리하기 위한 List를 갖고 있으며 아래의 코드와 같이 소멸자에서 자식들을 모두 삭제해버립니다.

QObject::~QObject()
{
    ...
	
    if ( childObjects ) {                // delete children objects
        QObjectListIt it(*childObjects);
        while ( (obj=it.current()) ) {
            ++it;
            obj->parentObj = 0;
            delete obj;
            if ( !childObjects )        // removeChild resets it
                break;
	}
        delete childObjects;
    }
    
    ...
}

문제는 이런 내부적인 처리를 무시한채 다음과 같은 부모-자식 관계를 갖고 있는 객체를 직접 소멸하려 할 때 발생합니다.

RoundButton::RoundButton(QWidget* parent) : QWidget(parent)
{
    ...
	
    m_container = new QWidget(this);
    ...      
    
    m_button = new QPushButton(m_container);    // m_container를 부모 위젯으로 지정 
	
    ...
}

RoundButton::~RoundButton()
{
	delete m_container;
	delete m_button;	// double deletion 
}

m_container는 m_button을 자식으로 갖습니다. QWidget간의 부모-자식 관계(상속관계의 부모-자식이 아닌)를 객체 생성시 위 코드와 같이 지정할 수 있습니다. 이 코드는 소멸자에서 객체의 소멸 순서에 따라 double deletion이 발생하게 됩니다. m_container를 먼저 삭제해버리면 최상위 클래스인 QObject의 소멸자가 m_button을 삭제하기 때문에 제가 직접 추가한 delete m_button 시점에는 이미 객체가 소멸된 상태가 됩니다.

따라서 직접 메모리를 해제하고 싶다면 각 객체의 관계를 잘 염두해 두고 메모리를 해제해야 합니다. 조금만 따져보면 객체간의 관계를 파악할 수 있기 때문에 큰 고민거리가 아니긴 하지만 이런 고민이 번거롭다면 메모리 해제를 프레임워크에 맡기는 것이 좋습니다.

제가 Java나 Python을 먼저 접했더라면 직접 메모리를 해제하려는 것이 의아했을텐데? 이것도 익숙함과 습관 탓이겠죠? :)

참고 페이지



저작자 표시 비영리 변경 금지
신고
Posted by 완소타코

댓글을 달아 주세요

I want you back에 이은 Jackson 5의 두번째 베이스 커버곡입니다. 제가 태어나기도 훨씬전인 1970년, 마이클 잭슨이 11살 때 부른 노래라고 하네요. 베이스 라인은 I want you back과 유사한 느낌을 갖고 있습니다. 어린 시절의 마이클 잭슨 목소리를 듣고 있으니 영혼이 맑아지는 것 같습니다. 아이러니하게도 마이클 잭슨은 어린 시절을 아버지의 학대로 불우하게 보냈는데 말이죠. ㅠ




저작자 표시 비영리 변경 금지
신고

'Bass Cover' 카테고리의 다른 글

[Bass Cover] Jackson 5 - ABC  (2) 2012.02.13
[Bass Cover] Maroon 5 - Won't Go Home Without you  (1) 2011.12.16
[Bass Cover] Jackson 5 - I Want You Back  (0) 2011.09.21
[Bass Cover] Maroon 5 - This Love  (0) 2011.09.05
Posted by 완소타코

댓글을 달아 주세요

  1. asbubam 2012.02.15 13:45 신고  댓글주소  수정/삭제  댓글쓰기

    잘은 모르지만, 크로매틱을 많이 하셨는 지, 운지가 많이 안정되어 있어 보이네요. 그리고 잘 치시네요 :)

PySide에서는 pyside-rcc라는 Qt Resource Compiler를 제공합니다. pyside-rcc로 Qt 리소스 파일( *.qrc)을 파이썬 파일로 변환해 소스에서 사용할 수 있습니다. 사용 방법은 다음과 같습니다.

$] pyside-rcc ./application.rc -o ./app

시스템에 pyside-rcc 명령어가 없으면 다음 명령어로 설치(우분투) 할 수 있습니다.

$] sudo apt-get install pyside-tools

다음 코드는 PySide example 폴더에 있는 application이라는 예제입니다. 코드를 살펴보면 qrc 파일을 어떻게 사용하는지 살펴볼 수 있습니다.  

리소스 파일에 대한 정보를 담고 있는 application.qrc


    images/copy.png
    images/cut.png
    images/new.png
    images/open.png
    images/paste.png
    images/save.png



application.py
#!/usr/bin/env python

# This is only needed for Python v2 but is harmless for Python v3.
#import sip
#sip.setapi('QVariant', 2)

from PySide import QtCore, QtGui

import application_rc


class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.curFile = ''

        self.textEdit = QtGui.QTextEdit()
        self.setCentralWidget(self.textEdit)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()

        self.readSettings()

        self.textEdit.document().contentsChanged.connect(self.documentWasModified)

        self.setCurrentFile('')
        self.setUnifiedTitleAndToolBarOnMac(True)

    def closeEvent(self, event):
        if self.maybeSave():
            self.writeSettings()
            event.accept()
        else:
            event.ignore()

    def newFile(self):
        if self.maybeSave():
            self.textEdit.clear()
            self.setCurrentFile('')

    def open(self):
        if self.maybeSave():
            fileName, filtr = QtGui.QFileDialog.getOpenFileName(self)
            if fileName:
                self.loadFile(fileName)

    def save(self):
        if self.curFile:
            return self.saveFile(self.curFile)

        return self.saveAs()

    def saveAs(self):
        fileName, filtr = QtGui.QFileDialog.getSaveFileName(self)
        if fileName:
            return self.saveFile(fileName)

        return False

    def about(self):
        QtGui.QMessageBox.about(self, "About Application",
                "The Application example demonstrates how to write "
                "modern GUI applications using Qt, with a menu bar, "
                "toolbars, and a status bar.")

    def documentWasModified(self):
        self.setWindowModified(self.textEdit.document().isModified())

    def createActions(self):
        self.newAct = QtGui.QAction(QtGui.QIcon(':/images/new.png'), "&New",
                self, shortcut=QtGui.QKeySequence.New,
                statusTip="Create a new file", triggered=self.newFile)

        self.openAct = QtGui.QAction(QtGui.QIcon(':/images/open.png'),
                "&Open...", self, shortcut=QtGui.QKeySequence.Open,
                statusTip="Open an existing file", triggered=self.open)

        self.saveAct = QtGui.QAction(QtGui.QIcon(':/images/save.png'),
                "&Save", self, shortcut=QtGui.QKeySequence.Save,
                statusTip="Save the document to disk", triggered=self.save)

        self.saveAsAct = QtGui.QAction("Save &As...", self,
                shortcut=QtGui.QKeySequence.SaveAs,
                statusTip="Save the document under a new name",
                triggered=self.saveAs)

        self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
                statusTip="Exit the application", triggered=self.close)

        self.cutAct = QtGui.QAction(QtGui.QIcon(':/images/cut.png'), "Cu&t",
                self, shortcut=QtGui.QKeySequence.Cut,
                statusTip="Cut the current selection's contents to the clipboard",
                triggered=self.textEdit.cut)

        self.copyAct = QtGui.QAction(QtGui.QIcon(':/images/copy.png'),
                "&Copy", self, shortcut=QtGui.QKeySequence.Copy,
                statusTip="Copy the current selection's contents to the clipboard",
                triggered=self.textEdit.copy)

        self.pasteAct = QtGui.QAction(QtGui.QIcon(':/images/paste.png'),
                "&Paste", self, shortcut=QtGui.QKeySequence.Paste,
                statusTip="Paste the clipboard's contents into the current selection",
                triggered=self.textEdit.paste)

        self.aboutAct = QtGui.QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = QtGui.QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=QtGui.qApp.aboutQt)

        self.cutAct.setEnabled(False)
        self.copyAct.setEnabled(False)
        self.textEdit.copyAvailable.connect(self.cutAct.setEnabled)
        self.textEdit.copyAvailable.connect(self.copyAct.setEnabled)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator();
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newAct)
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.saveAct)

        self.editToolBar = self.addToolBar("Edit")
        self.editToolBar.addAction(self.cutAct)
        self.editToolBar.addAction(self.copyAct)
        self.editToolBar.addAction(self.pasteAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        settings = QtCore.QSettings("Trolltech", "Application Example")
        pos = settings.value("pos", QtCore.QPoint(200, 200))
        size = settings.value("size", QtCore.QSize(400, 400))
        self.resize(size)
        self.move(pos)

    def writeSettings(self):
        settings = QtCore.QSettings("Trolltech", "Application Example")
        settings.setValue("pos", self.pos())
        settings.setValue("size", self.size())

    def maybeSave(self):
        if self.textEdit.document().isModified():
            ret = QtGui.QMessageBox.warning(self, "Application",
                    "The document has been modified.\nDo you want to save "
                    "your changes?",
                    QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard |
                    QtGui.QMessageBox.Cancel)
            if ret == QtGui.QMessageBox.Save:
                return self.save()
            elif ret == QtGui.QMessageBox.Cancel:
                return False
        return True

    def loadFile(self, fileName):
        file = QtCore.QFile(fileName)
        if not file.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(self, "Application",
                    "Cannot read file %s:\n%s." % (fileName, file.errorString()))
            return

        inf = QtCore.QTextStream(file)
        QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
        self.textEdit.setPlainText(inf.readAll())
        QtGui.QApplication.restoreOverrideCursor()

        self.setCurrentFile(fileName)
        self.statusBar().showMessage("File loaded", 2000)

    def saveFile(self, fileName):
        file = QtCore.QFile(fileName)
        if not file.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(self, "Application",
                    "Cannot write file %s:\n%s." % (fileName, file.errorString()))
            return False

        outf = QtCore.QTextStream(file)
        QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
        outf << self.textEdit.toPlainText()
        QtGui.QApplication.restoreOverrideCursor()

        self.setCurrentFile(fileName);
        self.statusBar().showMessage("File saved", 2000)
        return True

    def setCurrentFile(self, fileName):
        self.curFile = fileName
        self.textEdit.document().setModified(False)
        self.setWindowModified(False)

        if self.curFile:
            shownName = self.strippedName(self.curFile)
        else:
            shownName = 'untitled.txt'

        self.setWindowTitle("%s[*] - Application" % shownName)

    def strippedName(self, fullFileName):
        return QtCore.QFileInfo(fullFileName).fileName()


if __name__ == '__main__':

    import sys

    app = QtGui.QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit(app.exec_())

위 코드에서 하이라이트 되어있는 부분이 Qt Resource와 관련된 코드들입니다. application_rc.py 파일을 import 한 뒤, ":/images/copy.png"와 같은 방식으로 사용하는 것을 볼 수 있습니다. (소스 코드에서 리소스 사용 방법은 Qt와 동일)

실행 결과


저작자 표시 비영리 변경 금지
신고
Posted by 완소타코

댓글을 달아 주세요

  1. asbubam 2012.02.10 09:52 신고  댓글주소  수정/삭제  댓글쓰기

    타코님 뜬금없는 질문이지만,
    파이썬을 쉽고 재미있게 배울 수 있는 방법은 뭐가 있을까요?
    꼭 배워보고싶은 언어인데, 기존 소스를 아주조금 수정해서 사용하는 것 말고는
    제대로 배워본 적이 없어서요.
    타코님이라면 재미있는 조언을 해주실 것 같아 여쭤봅니다~!

    • 완소타코 2012.02.10 13:39 신고  댓글주소  수정/삭제

      재미있는 조언을 드리고 싶은데, 저도 보편적인 방법밖에 생각이 안나네요.
      저는 입문서 성격의 책과 튜토리얼를 살펴보면서 사용법을 익히고, 간단하게 뭔가를 만들면서 언어를 배우는 방식이 효율적이고 재미있었던 것 같습니다.

      미니프로젝트 같은 간단한 아이템을 정해서 시작해보는 건 어떨까요? 꼭 새로운게 아니더라도 기존의 것들을 파이썬으로 만들어 보는 것도 좋을 것 같아요. :)

한발 늦은 Beagle Term 소개 포스팅입니다. Beagle Term은 크롬 확장과 오픈 소스 라이브러리를 사용해 크롬 브라우저 기반의 SSH 클라이언트를 만들기 위한 프로젝트입니다. 2011년 구글 코리아 해커톤에 참여한 계기로 시작하게 되었습니다. (해커톤이 순위를 정하는 대회는 아니었지만 참가자 투표에서 3등을 하는 바람에 저에게는 정말 잊을 수 없는 사건이 돼버렸습니다 :D)

아래 동영상을 통해 Beagle Term의 프로토타입 버전을 살펴볼 수 있습니다.



VT100 호환 터미널을 웹으로 구현하는 것이 생각보다 녹록하지 않아서 어려움이 있었습니다. 해커톤 이후에도 작업을 진행했었는데, 아직 이렇다할 진전은 없습니다. (지금은 우선 순위에 밀려서 잠시 중단 상태) 마음 같아서는 저도 얼른 완성된 모습을 보고 싶은데, 이런저런 일들이 많아서 생각처럼 쉽지가 않네요. 틈틈히 만들고 있는 개인 프로젝트들이 좋은 방향으로 마무리 돼서 Beagle Term에 투자할 수 있는 시간이 개인적으로도 빨리 찾아왔으면 좋겠습니다.

저작자 표시 비영리 변경 금지
신고

'bugfactory > beagleTerm' 카테고리의 다른 글

[bugFactory] Beagle Term 프로토타입 소개  (6) 2012.02.06
Posted by 완소타코

댓글을 달아 주세요

  1. javarouka 2012.02.08 10:18 신고  댓글주소  수정/삭제  댓글쓰기

    참여하기로 해놓고 구경만 하고 있네요.
    회사일 외에 따로 개인적으로 프로젝트를 진행하시는 분들을 보면 멋지다는 생각을 합니다.

  2. asbubam 2012.02.09 09:12 신고  댓글주소  수정/삭제  댓글쓰기

    제 개인적으로도, Beagle Term 프로젝트를 통해서, 검색을 하다 타코님 블로그를 알게되서 빠이팅(파이팅 아님) 하게되었기 때문에, 기대도 되고 의미도 있는 프로젝트 입니다. :)
    얼른 시간의 여유가 생기셔서 진행상황을 더 알려주시면 좋겠네요.

    • 완소타코 2012.02.09 12:56 신고  댓글주소  수정/삭제

      다시 찾아주셔서 감사합니다. 링크를 타고 가보니 asbubam님도 뭔가 준비 중이시네요 :D
      완료되면 소식 전해주세요~ 제가 열심히 테스트 해드리겠습니다. ㅎ



티스토리 툴바